日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

C語言?超詳細順序表的模擬實現實例建議收藏_C 語言

作者:鹿九丸 ? 更新時間: 2022-05-28 編程語言

概念及結構

順序表是用一段物理地址連續的存儲單元依次存儲數據元素的線性結構,一般情況下采用數組存儲。在數組 上完成數據的增刪查改。

順序表一般可以分為:

靜態順序表:使用定長數組存儲元素,元素的數目無法進行修改。

//順序表的靜態存儲
#define N 7
typedef int SLDataType;
typedef struct SeqList
{
	SLDataType array[N];//定長數組
	size_t size;//有效數據的個數
}SeqList;

動態順序表

//順序表的動態存儲
typedef struct SeqList
{
	SLDataType* array;//指向動態開辟的數組,空間不夠可以增容
	size_t size;//有效數據的個數
	size_t capacity;//容量空間的大小
};

接口實現

靜態順序表只適用于確定知道需要存多少數據的場景。靜態順序表的定長數組導致N定大了,空間開多了浪 費,開少了不夠用。所以現實中基本都是使用動態順序表,根據需要動態的分配空間大小,所以下面我們實現動態順序表。

1 順序表的動態存儲

typedef int SLDataType;//順序表中存儲的數據,此處假設是int型
typedef struct SeqList
{
	int* a;//指向動態開辟的數組空間,空間可以隨時增容
	int size;//存儲數據個數
	int capacity;//存儲空間大小
}SL,SeqList;

2 順序表初始化

void SeqListInit(SeqList* psl);//聲明
void SeqListInit(SeqList* psl)
{
	assert(psl);//進行斷言是因為當psl為NULL時,下面的操作將無法進行,因為空指針是無法進行解引用的。
	psl->a = NULL;
	psl->size = 0;
	psl->capacity = 0;
}//函數實現

注意:進行斷言是因為當psl為NULL時,下面的操作將無法進行,因為空指針是無法進行解引用的,后面也是如此。

3 順序表的銷毀

void SeqListDestroy(SeqList* psl);
void SeqListDestroy(SeqList* psl)
{
	assert(psl);
	free(psl->a);
	psl->a = NULL;
	psl->capacity = psl->size = 0;
}

注意:free后面括號中的指針必須是malloc開辟出來的那塊空間,且不能有任何的偏差(即指針不能發生移動)。

下面進行舉例:

image-20220313121909217

像上面這樣使用是完全沒有問題的,但是像下面這樣進行使用就出現了問題:

image-20220313122035082

tmp進行自增操作后,就指向了下圖所示位置:

image-20220313122258326

free的位置是tmp++后的位置,這不符合C語言的規定,且即使正常的釋放掉了,前面的那一塊int空間也將引起內存泄漏問題,即動態開辟的內存忘記釋放。

4 順序表的尾插

void SeqListPushBack(SeqList* psl,SLDataType x);//聲明
void SeqListPushBack(SeqList* psl, SLDataType x)
{
	assert(psl);
	//如果滿了,就進行擴容
	SeqListCheckCapacity(psl);
	psl->a[psl->size] = x;
	psl->size++;
}

image-20220313123214723

5 順序表的尾刪

void SeqListPopBack(SeqList* psl);
void SeqListPopBack(SeqList* psl)
{
	assert(psl);
	if(psl->size > 0)
	{
		psl->size -= 1;
	}
}

6 順序表的頭插

void SeqListPushFront(SeqList* psl, SLDataType x);
void SeqListPushFront(SeqList* psl, SLDataType x)
{
	assert(psl);
	SeqListCheckCapacity(psl);
	int end = psl->size - 1;
	while (end >= 0)
	{
		psl->a[end+1] = psl->a[end];
		--end;
	}
	psl->a[0] = x;
	psl->size++;
}

順序表的頭插會涉及到后續元素的移動,頭插時要將順序表中的元素從后面開始進行移動,因為從前面開始移動的話會出現覆蓋現象。下面是圖示:

image-20220313150759211

7 順序表的頭刪

同理,如果想要元素不被覆蓋,就只能從前向后進行移動。

void SeqListPopFront(SeqList* psl);
void SeqListPopFront(SeqList* psl)
{
	//挪出數據,騰出頭部空間
	//方法一:從1開始移動
	/*assert(psl);
	if (psl->size > 0)
	{
		int begin = 1;
		while (begin < psl->size)
		{
			psl->a[begin - 1] = psl->a[begin];
			begin++;
		}
		psl->size--;
	}*/
	//方法二:從0開始移動
	assert(psl);
	if (psl->size > 0)
	{
		int begin = 0;
		while (begin < psl->size - 1)
		{
			psl->a[begin] = psl->a[begin + 1];
			begin++;
		}
		psl->size--;
	}
}

下圖是兩種移動方式的區別:

image-20220313152013487

問:為什么不可以直接將指針進行加1操作?

  • free釋放空間時的指針和malloc開辟空間的指針必須相同
  • mallo開辟的空間存在浪費,即0的那塊空間被浪費,無法進行使用,因為那塊空間是被合法申請的。

8 順序表容量的檢查與擴容

void SeqListCheckCapacity(SeqList* psl);
void SeqListCheckCapacity(SeqList* psl)
{
	assert(psl);
	if (psl->capacity == psl->size)
	{
		size_t newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(psl->a, sizeof(SLDataType) * newCapacity);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		else
		{
			psl->a = tmp;
			psl->capacity *= 2;
		}
	}
}

注意點1:此處考慮使用的是如果容量不夠,就將容量擴容為原容量的兩倍,但是最開始的容量是0,所以要考慮到最開始為0的情況。

注意點2:要對擴容是否成功進行檢測,即判斷剛申請的空間是否為空。

9 順序表任意位置的插入

void SeqListInsert(SeqList* psl,size_t pos,SLDataType x);
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x)
{
	assert(psl);
	//較為溫和的檢查方式
	/*if (pos > psl->size)
	{
		exit(-1);
	}*/
	assert(pos <= psl->size);//較為暴力的檢查方式
	SeqListCheckCapacity(psl);
	size_t end = psl->size;
	while (end > pos)
	{
		psl->a[end] = psl->a[end-1];
		--end;
	}
	psl->a[pos] = x;
	psl->size++;
}

注意:

問:為什么end從psl->size開始?

答:此處需要注意的是pos和end的類型,為什么呢?因為兩者都是無符號類型,所以尤其注意當end到了-1的時候,就會變成一個很大的數字,此時如果以此作為下標進行訪問,一定會出現越界訪問內存的情況,考慮一種極端情況,當pos為0的時候,end最小的時候就是為0,所以此時不會出現越界的情況,但是如果end是從psl->size - 1開始的話,那么while循環體內的語句就變成下面這樣:

while(end > pos)
{
	psl->a[end+1] = psl->a[end];
	--end;
}

最后end的最小值會變成-1,但是因為end是size_t類型,所以會變成一個很大的數字,在whle()循環條件判定時條件始終是滿足的,同時在進入循環體內之后,會出現越界訪問內存的操作。所以兩種情況的圖如下所示:

image-20220311164610452

10 順序表任意位置的刪除

void SeqListErase(SeqList* psl, size_t pos);
void SeqListErase(SeqList* psl, size_t pos)
{
	assert(psl);
	assert(pos <= psl->size);
	size_t begin = pos+1;
	while (begin < psl->size)
	{
		psl->a[begin-1] = psl->a[begin];
		++begin;
	}
	psl->size--;
}

11 順序表的打印

void SeqListPrint(SeqList* psl);
void SeqListPrint(SeqList* psl)
{
	assert(psl);
	for (int i = 0; i < psl->size; i++)
	{
		printf("%d ", psl->a[i]);
	}
	printf("\n");
}

12 順序表元素的查找

int SeqListFind(SeqList* psl,SLDataType x);
int SeqListFind(SeqList* psl, SLDataType x)
{
	assert(psl);
	for (int i = 0; i < psl->size; i++)
	{
		if (psl->a[i] == x)
			return i;//找到了對應元素,返回相應的下標
	}
	return -1;//說明沒有找到對應的元素
}

13 順序表元素的修改

void SeqListModify(SeqList* psl, size_t pos, SLDataType x);
void SeqListModify(SeqList* psl, size_t pos, SLDataType x)
{
	assert(psl);
	assert(pos < psl->size);
	psl->a[pos] = x;
}

原文鏈接:https://blog.csdn.net/m0_57304511/article/details/123460490

欄目分類
最近更新