首頁 > 軟體

C++ 資料結構超詳細講解順序表

2022-03-25 13:00:57

(●’◡’●)

前言

線性表是n個具有相同特性的資料元素的有限序列。線性表是一種在實際中廣泛使用的資料結構,常見的線性表:順序表、連結串列、棧、佇列、字串。

線性表在邏輯上是線性結構,也就是說連續的一條直線,但是在物理結構並不一定是連續的,線性表在物理上儲存時,通常以陣列和鏈式結構的形式儲存。

本章我們來深度初體驗順序表

一、順序表是什麼

概念及結構

順序表是一段實體地址連續的儲存單元依次儲存資料元素的線性結構,一般情況下采用陣列儲存。在陣列上完成資料的增刪查改。

1.靜態順序表:使用定長陣列儲存元素

2.動態順序表:使用動態開闢的陣列儲存

二、順序表的實現

基本結構

typedef int SLDataType;
// 順序表的動態儲存
typedef struct SeqList
{
SLDataType* array; // 指向動態開闢的陣列
size_t size ; // 有效資料個數
size_t capicity ; // 容量空間的大小
}SeqList;

介面實現

靜態順序表只適用於確定知道需要多少數劇的場景。靜態順序表的定長陣列導致N定大了,空間開多了浪費,開少了不夠用。所以我們基本使用動態順序表

typedef int SLDataType;
// 順序表的動態儲存
typedef struct SeqList
{
SLDataType* array; // 指向動態開闢的陣列
size_t size ; // 有效資料個數
size_t capicity ; // 容量空間的大小
}SeqList;
// 基本增刪查改介面
// 順序表初始化
void SeqListInit(SeqList* psl, size_t capacity);
// 檢查空間,如果滿了,進行增容
void CheckCapacity(SeqList* psl);
// 順序表尾插
void SeqListPushBack(SeqList* psl, SLDataType x);
// 順序表尾刪
void SeqListPopBack(SeqList* psl);
// 順序表頭插
void SeqListPushFront(SeqList* psl, SLDataType x);
// 順序表頭刪
void SeqListPopFront(SeqList* psl);
// 順序表查詢
int SeqListFind(SeqList* psl, SLDataType x);
// 順序表在pos位置插入x
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);
// 順序表刪除pos位置的值
void SeqListErase(SeqList* psl, size_t pos);
// 順序表銷燬
void SeqListDestory(SeqList* psl);
// 順序表列印
void SeqListPrint(SeqList* psl);

順序表列印

普通的通過連結串列的陣列列印

void SeqListPrint(SeqList* ps1)
{
	assert(ps1);

	for (int i = 0; i < ps1->size; ++i)
	{
		printf("%d", ps1->a[i]);
	}
	printf("n");
}

順序表初始化

置空

void SeqListInit(SeqList* ps1)
{
	assert(ps1);
	ps1->a = NULL;
	ps1->size = 0;
	ps1->capacity = 0;
}

順序表銷燬

順序表本質是陣列,是一片連續的儲存空間,頭部操作置空即可

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

動態擴容

由於是動態順序表,就是為了控制長度來解決問題,對順序表的操作大多離不開擴容

由於順序表是連續的如果使用malloc會出現異地擴容的現象,realloc雖然也存在異地擴,但會返回一片連續空間的首地址.

void SeqListCheckCapacity(SeqList* ps1)
{
	assert(ps1);
	if (ps1->size == ps1->capacity)//滿了
	{
		size_t newCapacity = ps1->capacity == 0 ? 4 : ps1->capacity * 2;//兩倍擴容
		SLDateType* tmp = realloc(ps1->a, sizeof(SLDateType) * newCapacity);
		if (tmp == NULL)//擴容失敗
		{
			printf("realloc failn");
			exit(-1);
		}
		else//擴容成功
		{
			ps1->a = tmp;
			ps1->capacity = newCapacity;
		}
	}
}

在pos位置插入x

對頭插尾插幫助極大

要判斷是否可以插入以及有沒有必要插入

過程圖示

void SeqListInsert(SeqList* ps1, size_t pos, SLDateType x)
{
	if (pos > ps1->size)//如圖
	{
		printf("越界:pos %dn", pos);
		return;
	}
	SeqListCheckCapacity(ps1);//插入即要擴容
	size_t end = ps1->size;
	while (end > pos)
	{
		ps1->a[end] = ps1->a[end - 1];//資料後挪,騰出空間
		--end;
	}
	//end=pos
	ps1->a[pos] = x;
	ps1->size++;//新增資料,需要增長
}

刪除pos位置

對頭刪尾刪幫助極大

//注意,順序表只注意size以前,size以後無硬性要求可以寬容對待

void SeqListErase(SeqList* ps1, size_t pos)
{
	assert(ps1);
	assert(pos < ps1->size);//同插入,需要有東西可刪

	size_t begin = pos + 1;
	while (begin < ps1->size)//由後向前覆蓋
	{
		ps1->a[begin - 1] = ps1->a[begin];
		++begin;
	}
	ps1->size--;
}

順序表尾插

相當於上文在size處插入資料,呼叫即可

void SeqListPushBack(SeqList* ps1, SLDateType x)
{
	assert(ps1);
	SeqListInsert(ps1, ps1->size, x);
}

順序表尾刪

注意辨別size的位置

void SeqListPopBack(SeqList* ps1)
{
	assert(ps1);
	SeqListErase(ps1, ps1->size-1);
}

順序表頭插

void SeqListPushFront(SeqList* ps1, SLDateType x)
{
	assert(ps1);
	SeqListInsert(ps1, 0, x);
}

順序表頭刪

//只需考慮size有效資料前

void SeqListPopFront(SeqList* ps1)
{
	assert(ps1);
	SeqListErase(ps1, 0);
}

查詢資料x

int SeqListFind(SeqList* ps1, SLDateType x)
{
	assert(ps1);
	for (int i = 0; i < ps1->size; ++i)
	{
		if (ps1->a[i] == x)
		{
			return i;//返回下標
		}
	}
	return -1;//沒找到
}


順序表的缺點

1. 中間/頭部的插入刪除,時間複雜度為O(N)
2. 增容需要申請新空間,拷貝資料,釋放舊空間。會有不小的消耗。
3. 增容一般是呈2倍的增長,勢必會有一定的空間浪費。例如當前容量為100,滿了以後增容到200,我們再繼續插入了5個資料,後面沒有資料插入了,那麼就浪費了95個資料空間

由此,前輩們總結出了連結串列

幾道練手題

原地移除陣列中所有的元素val,要求時間複雜度為O(N),空間複雜度為O(1)。題目連結

刪除排序陣列中的重複項。連結

合併兩個有序陣列.題目連結

總結

學習程式設計,解決問題,資料結構與演演算法正是非常好的工具。模擬實現正是對自身程式碼能力的鍛鍊提升,也對其有了更深的理解.競賽的話,我認為要先對知識進行系統深度的學習,才可隨機應變。不可盲目刷題,有些深層原理可能與做題所理解出的出入極大。

到此這篇關於C++ 資料結構超詳細講解順序表的文章就介紹到這了,更多相關C++ 順序表內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com