【動手實現系列】手撕ArrayList

說到前面

學過Java的同學就會知道,ArrayList是Java中的集合。
ArrayList就是動態數組,它的底層是數組實現,我們來閱讀一下ArrayList的源碼。
先看它的無參構造方法:

	public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

其中的DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一個Object類型的空數組。

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

所以通過無參構造方法就創建了一個Object類型的空數組,再看add()方法:

	public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

在往數組中添加元素之前,它調用了ensureCapacityInternal()方法,所以來看看該方法:

	private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

該方法又繼續調用了ensureExplicitCapacity()方法,通過calculateCapacity()方法計算出容量:

	private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

從傳遞過來的參數和DEFAULT_CAPACITY中找出最大值,方法參數爲當前數組的大小,DEFAULT_CAPACITY爲數組的初始容量:

private static final int DEFAULT_CAPACITY = 10;

綜上所述,ArrayList先初始化了一個空數組,然後在添加元素之前先申請足夠的內存空間,初始申請的容量爲10。

其它方法比較簡單,就不一一分析了。
由於ArrayList是數組的實現,所以查找快,但增刪慢。

實現ArrayList

數據結構專欄的文章已經更新了好幾篇了,本篇文章我們就來實現一下ArrayList,檢驗一下自己的學習進度,操作類似於順序表,所以如果你把順序表掌握了,那麼實現ArrayList就會很簡單。

基本操作

我們照着ArrayList的API,把它的方法基本實現一遍:

結構定義

#define InitializeSize 10		//初始容量爲10

typedef struct{
	int *data;		//動態數組
	int length;		//有效元素長度
	int size;		//預分配數組大小
}ArrayList,*PArrayList;

該結構共有三個變量,第一個data表示動態數組,第二個length表示集合的當前元素個數,第三個size表示集合的最大容量。

初始化集合

前面分析了ArrayList的初始化方式,這裏我們自己實現的話可以簡單一點,初始直接申請容量爲10的數組內存,代碼實現如下:

//初始化集合
ArrayList CreateList(){
	ArrayList list;
	//分配動態數組內存
	list.data = (int*) malloc(sizeof(int) * InitializeSize);
	if(list.data == NULL){
		exit(-1);
	}
	//初始化長度和大小
	list.length = 0;
	list.size = InitializeSize;
	return list;
}

對應於ArrayList的無參構造方法。

初始化指定容量大小的集合

ArrayList還有指定容量大小的構造方法,通過傳入的參數申請數組內存空間:

//構造一個具有指定初始容量的空列表
ArrayList CreateListOfInitialCapacity(int initialCapacity){
	ArrayList list;
	//分配動態數組內存
	list.data = (int*) malloc(sizeof(int) * initialCapacity);
	if(list.data == NULL){
		exit(-1);
	}
	//初始化長度和大小
	list.length = 0;
	list.size = initialCapacity;
	return list;
}

這個很簡單,稍微修改一下即可。

添加元素

接下來我們實現ArrayList的add()方法,add()方法分爲兩種:

  1. 將元素添加到集合中的指定位置
  2. 將元素直接添加到集合尾部

將元素添加到集合中的指定位置

在添加元素之前,我們需要判斷當前集合是否滿,如果滿了,則申請內存空間,我們就規定申請原來數組的兩倍內存即可:

int AddOfIndexList(PArrayList pList,int pos,int val){
	int i;
	//判斷pos值的合法性
	if(pos < 0 || pos > pList->length){
		return 0;
	}
	//調用擴容函數
	CapacityList(pList);
	//將插入位置後面的元素都向後移動一位
	for(i = pList->length;i > pos;--i){
		//元素後移
		pList->data[i] = pList->data[i - 1];
	}
	//插入元素值
	pList->data[pos] = val;
	//有效元素長度加1
	pList->length++;
	return 1;//添加成功,返回1
}

先判斷指定的位置是否符合當前集合,然後調用擴容函數,接着講插入位置後面的元素都向後移動一位,最後將指定位置的元素值修改爲添加的元素值,記得集合元素個數加1。

下面是擴容函數:

void CapacityList(PArrayList pList){
	int *tmp, i, *p, *q;
    //若有效元素長度等於數組大小,則爲集合擴容
    if (pList->length >= pList->size) {
    	//申請原數組兩倍的存儲空間作爲新集合
        tmp = (int *)malloc(sizeof(int) * pList->size * 2);
        //變量p暫時存放原數組
        p = pList->data;
        //變量q暫時存放新數組
        q = tmp;
        //將原數組的元素值複製到新數組
        for (i = 0; i < pList->length; i++) {
        	//複製元素
           	*q = *p;
           	//地址後移
            p++;
            q++;
        }
        //釋放原數組內存
        free(pList->data);
        //集合的數組指針指向新數組
        pList->data = tmp;
        //數組大小變爲原來的兩倍
        pList->size = pList->size * 2;
    }
}

來分析一下,首先我們需要判斷當前集合是否滿了,判斷條件爲length == size,也就是當集合中的元素個數等於集合的最大容量時,說明集合滿了。此時就去申請原數組的兩倍內存空間作爲新集合。

這裏需要注意的是要用兩個變量去分別存儲原數組和新數組,然後通過循環將原數組的元素值複製到新數組中,最後釋放原數組的內存,然後讓data指向新數組,記得集合容量乘2(如果不用變量存儲,會直接改變兩個數組的地址,從而操作失敗)。

將元素直接添加到集合尾部

添加到集合尾部就很簡單了,直接看代碼:

void AddList(PArrayList pList,int val){
	//調用AddListAsLocate()函數即可,位置爲有效元素長度
	AddOfIndexList(pList,pList->length,val);
}

這就是爲什麼我要先實現指定位置的元素插入了,這樣在直接添加到集合尾部的函數中直接調用原先的函數就可以了,位置爲集合的尾部。

移除集合中的所有元素

移除集合中的所有元素非常簡單,直接將數組中的所有元素看做無效即可,將元素個數置爲0:

void ClearList(PArrayList pList){
	//將有效元素長度置爲0
	pList->length = 0;
}

返回集合中首次出現的指定元素的索引

int IndexOfList(PArrayList pList,int val){
	int i;
	//遍歷集合中的元素值
	for(i = 0;i < pList->length;++i){
		//查找元素值
		if(pList->data[i] == val)
			return i;
	}
	return 0;
}

通過遍歷集合中的所有元素來匹配指定的元素值,若匹配成功,則返回索引,若無匹配成功,則返回0。

查找集合中是否包含指定的元素

int ContainsList(PArrayList pList,int val){
	//調用IndexOfList()函數
	if(IndexOfList(pList,val))
		return 1;
	else
		return 0;
}

該功能我們只需調用IndexOfList()函數即可,若返回值爲0,則說明集合中沒有指定的元素。

返回集合中指定位置上的元素

int GetList(PArrayList pList,int pos){
	//判斷pos值的合法性
	if(pos < 0 || pos > pList->length - 1){
		return 0;
	}
	//返回元素值
	return pList->data[pos];
}

某些操作非常簡單,我就不分析了。

判斷集合是否爲空

int ListEmpty(PArrayList pList){
	//判斷集合的有效元素長度是否爲0
	if(pList->length == 0)
		return 1;
	else
		return 0;
}

返回集合中最後一次出現的指定元素的索引

返回集合中指定元素最後一次出現的位置,我們可以從尾部到頭部遍歷集合,然後一一匹配元素值:

int LastIndexOfList(PArrayList pList,int val){
	int i;
	//從尾端開始遍歷集合中的元素值
	for(i = pList->length - 1;i >= 0;--i){
		//查找元素值
		if(pList->data[i] == val)
			return i;
	}
	return 0;
}

移除集合中指定位置上的元素

int RemoveOfIndexList(PArrayList pList,int pos){
	int i,val;
	//判斷pos值的合法性
	if(pos < 0 || pos > pList->length - 1){
		return 0;
	}
	//將刪除位置後面的元素都向前移動一位
	val = pList->data[pos];		//保存刪除位置的元素值
	for(i = pos;i < pList->length;++i){	
		pList->data[i] = pList->data[i + 1];
	}
	//有效元素長度減1
	pList->length--;
	return val;					//返回刪除的元素值
}

移除元素需要先將指定位置後面的元素都向前移動一位,在這之前,先將指定位置的元素值保存,否則將被後面的元素覆蓋,最後記得元素個數減1。

移除集合中首次出現的指定元素(如果存在)

int RemoveList(PArrayList pList,int val){
	int i;
	//遍歷集合中的元素
	for (i = 0; i < pList->length; ++i){
		if(pList->data[i] == val){
			//調用RemoveOfIndexList()函數
			RemoveOfIndexList(pList,i);
			return 1;
		}
	}
	return 0;
}

用指定的元素替代集合中指定位置上的元素

int SetList(PArrayList pList,int pos,int val){
	//判斷pos值的合法性
	if(pos < 0 || pos > pList->length - 1){
		return 0;
	}
	//取出列表原位置上的元素
	int oldVal = pList->data[pos];
	//替換列表原位置上的元素
	pList->data[pos] = val;
	return oldVal;			//返回原位置上的元素
}

返回集合中的元素個數

int ListSize(PArrayList pList){
	//返回集合大小
	return pList->length;
}

移除集合中索引在 fromIndex(包括)和 toIndex(不包括)之間的所有元素

void RemoveRangeList(PArrayList pList,int fromIndex,int toIndex){
	int i,size;
	//判斷fromIndex和toIndex值的合法性
	if(fromIndex < 0 || toIndex > pList->length){
		return;
	}
	//size爲需要移除的元素個數
	size = toIndex - fromIndex; 
	for(i = 0;i < size;++i){
		//調用RemoveOfIndexList()函數,從fromIndex位置開始,刪除size個元素
		RemoveOfIndexList(pList,fromIndex);
	}
}

來分析一下該函數吧,假設我有這樣一個序列{1,2,3,4,5},要移除索引爲0到3的元素值,該如何實現呢?
首先我們移除索引爲0的元素值1,按道理我們需要移除索引爲1的元素值2,但是需要注意了,當你移除索引爲0的元素值1後,序列就變爲{2,3,4,5},此時移除元素2的索引仍然爲0。通過分析得知,我們只需知道需要移除的元素個數,然後從fromIndex開始,移除指定的元素個數即可。

將集合的容量調整爲集合的當前大小

void TrimToSizeList(PArrayList pList){
	int *tmp, i, *p, *q;
	//申請集合大小的存儲空間
	tmp = (int*)malloc(sizeof(int) * pList->length);
	//變量p暫時存放原數組
	p = pList->data;
	//變量q暫時存放新數組
	q = tmp;
	//將原數組的元素複製到新數組
 	for (i = 0; i < pList->length; i++) {
        //複製元素
        *q = *p;
        //地址後移
        p++;
        q++;
    }
    //釋放原數組內存
    free(pList->data);
    //集合的數組指針指向新數組
    pList->data = tmp;
    //數組大小變爲當前列表的大小
    pList->size = pList->length;
}

這裏和擴容函數的操作類似,就不具體分析了。

最後

到這裏,關於ArrayList的實現就結束了,還有部分ArrayList的方法未實現,大家可以自己嘗試一下。

源代碼

//C實現ArrayList

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

#define InitializeSize 10

typedef struct{
	int *data;		//動態數組
	int length;		//有效元素長度
	int size;		//預分配數組大小
}ArrayList,*PArrayList;

//函數聲明
ArrayList CreateList();//初始化集合
ArrayList CreateListOfInitialCapacity(int initialCapacity);//構造一個具有指定初始容量的空列表
void AddList(PArrayList pList,int val);//將指定的元素添加到此列表的尾部
int AddOfIndexList(PArrayList pList,int pos,int val);//將指定的元素插入此列表中的指定位置
void ClearList(PArrayList pList);//移除此集合中的所有元素
int ContainsList(PArrayList pList,int val);//如果此列表中包含指定的元素,則返回 1
int GetList(PArrayList pList,int pos);//返回此列表中指定位置上的元素
int IndexOfList(PArrayList pList,int val);//返回此列表中首次出現的指定元素的索引,或如果此列表不包含元素,則返回 0
int ListEmpty(PArrayList pList);//如果此列表中沒有元素,則返回 1
int LastIndexOfList(PArrayList pList,int val);//返回此列表中最後一次出現的指定元素的索引,或如果此列表不包含索引,則返回 0
int RemoveOfIndexList(PArrayList pList,int pos);//移除此列表中指定位置上的元素
int RemoveList(PArrayList pList,int val);//移除此列表中首次出現的指定元素(如果存在)
void RemoveRangeList(PArrayList pList,int fromIndex,int toIndex);//移除列表中索引在 fromIndex(包括)和 toIndex(不包括)之間的所有元素
int SetList(PArrayList pList,int pos,int val);//用指定的元素替代此列表中指定位置上的元素
int ListSize(PArrayList pList);//返回此列表中的元素數
int* ListToArray(PArrayList pList);//按適當順序(從第一個到最後一個元素)返回包含此列表中所有元素的數組
void TrimToSizeList(PArrayList pList);//將此 ArrayList 實例的容量調整爲列表的當前大小。應用程序可以使用此操作來最小化 ArrayList 實例的存儲量
void TraverseList(PArrayList pList);//集合的遍歷操作


int main(int argc, char const *argv[])
{
	/* code */
	return 0;
}

//初始化集合
ArrayList CreateList(){
	ArrayList list;
	//分配動態數組內存
	list.data = (int*) malloc(sizeof(int) * InitializeSize);
	if(list.data == NULL){
		exit(-1);
	}
	//初始化長度和大小
	list.length = 0;
	list.size = InitializeSize;
	return list;
}

//爲集合擴容(通常在往集合中添加元素時進行集合擴容)
void CapacityList(PArrayList pList){
	int *tmp, i, *p, *q;
    //若有效元素長度等於數組大小,則爲集合擴容
    if (pList->length >= pList->size) {
    	//申請原數組兩倍的存儲空間作爲新集合
        tmp = (int *)malloc(sizeof(int) * pList->size * 2);
        //變量p暫時存放原數組
        p = pList->data;
        //變量q暫時存放新數組
        q = tmp;
        //將原數組的元素值複製到新數組
        for (i = 0; i < pList->length; i++) {
        	//複製元素
           	*q = *p;
           	//地址後移
            p++;
            q++;
        }
        //釋放原數組內存
        free(pList->data);
        //集合的數組指針指向新數組
        pList->data = tmp;
        //數組大小變爲原來的兩倍
        pList->size = pList->size * 2;
    }
}

//構造一個具有指定初始容量的空列表
ArrayList CreateListOfInitialCapacity(int initialCapacity){
	ArrayList list;
	//分配動態數組內存
	list.data = (int*) malloc(sizeof(int) * initialCapacity);
	if(list.data == NULL){
		exit(-1);
	}
	//初始化長度和大小
	list.length = 0;
	list.size = initialCapacity;
	return list;
}

//將指定的元素插入此列表中的指定位置
int AddOfIndexList(PArrayList pList,int pos,int val){
	int i;
	//判斷pos值的合法性
	if(pos < 0 || pos > pList->length){
		return 0;
	}
	//調用擴容函數
	CapacityList(pList);
	//將插入位置後面的元素都向後移動一位
	for(i = pList->length;i > pos;--i){
		//元素後移
		pList->data[i] = pList->data[i - 1];
	}
	//插入元素值
	pList->data[pos] = val;
	//有效元素長度加1
	pList->length++;
	return 1;//添加成功,返回1
}

//將指定的元素添加到此列表的尾部
void AddList(PArrayList pList,int val){
	//調用AddListAsLocate()函數即可,位置爲有效元素長度
	AddOfIndexList(pList,pList->length,val);
}

//如果此列表中沒有元素,則返回 1
int ListEmpty(PArrayList pList){
	//判斷集合的有效元素長度是否爲0
	if(pList->length == 0)
		return 1;
	else
		return 0;
}

//集合的遍歷操作
void TraverseList(PArrayList pList){
	int i;
	//判斷集合是否爲空
	if(ListEmpty(pList)){
		//輸出空集合
		printf("[]\n");
	}
	for(i = 0;i < pList->length;++i){
		//輸出元素值
		printf("%d\t",pList->data[i]);
	}
}

//移除此集合中的所有元素
void ClearList(PArrayList pList){
	//將有效元素長度置爲0
	pList->length = 0;
}

//返回此列表中指定位置上的元素
int GetList(PArrayList pList,int pos){
	//判斷pos值的合法性
	if(pos < 0 || pos > pList->length - 1){
		return 0;
	}
	//返回元素值
	return pList->data[pos];
}

//返回此列表中首次出現的指定元素的索引,或如果此列表不包含元素,則返回 0
int IndexOfList(PArrayList pList,int val){
	int i;
	//遍歷集合中的元素值
	for(i = 0;i < pList->length;++i){
		//查找元素值
		if(pList->data[i] == val)
			return i;
	}
	return 0;
}

//如果此列表中包含指定的元素,則返回 1
int ContainsList(PArrayList pList,int val){
	//調用IndexOfList()函數
	if(IndexOfList(pList,val))
		return 1;
	else
		return 0;
}

//返回此列表中最後一次出現的指定元素的索引,或如果此列表不包含索引,則返回 0
int LastIndexOfList(PArrayList pList,int val){
	int i;
	//從尾端開始遍歷集合中的元素值
	for(i = pList->length - 1;i >= 0;--i){
		//查找元素值
		if(pList->data[i] == val)
			return i;
	}
	return 0;
}

//移除此列表中指定位置上的元素
int RemoveOfIndexList(PArrayList pList,int pos){
	int i,val;
	//判斷pos值的合法性
	if(pos < 0 || pos > pList->length - 1){
		return 0;
	}
	//將刪除位置後面的元素都向前移動一位
	val = pList->data[pos];		//保存刪除位置的元素值
	for(i = pos;i < pList->length;++i){	
		pList->data[i] = pList->data[i + 1];
	}
	//有效元素長度減1
	pList->length--;
	return val;					//返回刪除的元素值
}

//移除此列表中首次出現的指定元素(如果存在)
int RemoveList(PArrayList pList,int val){
	int i;
	//遍歷集合中的元素
	for (i = 0; i < pList->length; ++i){
		if(pList->data[i] == val){
			//調用RemoveOfIndexList()函數
			RemoveOfIndexList(pList,i);
			return 1;
		}
	}
	return 0;
}

//移除列表中索引在 fromIndex(包括)和 toIndex(不包括)之間的所有元素
void RemoveRangeList(PArrayList pList,int fromIndex,int toIndex){
	int i,size;
	//判斷fromIndex和toIndex值的合法性
	if(fromIndex < 0 || toIndex > pList->length){
		return;
	}
	//size爲需要移除的元素個數
	size = toIndex - fromIndex; 
	for(i = 0;i < size;++i){
		//調用RemoveOfIndexList()函數,從fromIndex位置開始,刪除size個元素
		RemoveOfIndexList(pList,fromIndex);
	}
}

//用指定的元素替代此列表中指定位置上的元素
int SetList(PArrayList pList,int pos,int val){
	//判斷pos值的合法性
	if(pos < 0 || pos > pList->length - 1){
		return 0;
	}
	//取出列表原位置上的元素
	int oldVal = pList->data[pos];
	//替換列表原位置上的元素
	pList->data[pos] = val;
	return oldVal;			//返回原位置上的元素
}

//返回此列表中的元素數
int ListSize(PArrayList pList){
	//返回集合大小
	return pList->length;
}

//按適當順序(從第一個到最後一個元素)返回包含此列表中所有元素的數組
int* ListToArray(PArrayList pList){
	int i;
	//創建新數組
	int *newData = (int*) malloc(sizeof(int) * pList->length);
	if(newData == NULL){
		exit(-1);
	}
	//將集合中的元素放入數組
	for(i = 0;i < pList->length;++i){
		newData[i] = pList->data[i];
	}
	return newData;		//返回數組
}

//將此 ArrayList 實例的容量調整爲列表的當前大小。應用程序可以使用此操作來最小化 ArrayList 實例的存儲量
void TrimToSizeList(PArrayList pList){
	int *tmp, i, *p, *q;
	//申請集合大小的存儲空間
	tmp = (int*)malloc(sizeof(int) * pList->length);
	//變量p暫時存放原數組
	p = pList->data;
	//變量q暫時存放新數組
	q = tmp;
	//將原數組的元素複製到新數組
 	for (i = 0; i < pList->length; i++) {
        //複製元素
        *q = *p;
        //地址後移
        p++;
        q++;
    }
    //釋放原數組內存
    free(pList->data);
    //集合的數組指針指向新數組
    pList->data = tmp;
    //數組大小變爲當前列表的大小
    pList->size = pList->length;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章