好好學習基礎知識,出人頭地就靠它了,內外兼修。(好吧,我現在內外都不行)寫這篇文章的目的就是為了,鞏固剛學完的線性表,個人能力有限,若有不當之處,望指出。
線性表
好了,扯完了,說正事:
1、定義
線性表是一種及其常用的並且最簡單的一種數據結構。簡單來說,線性表就是集合里的元素的有限排列。(在這里我把集合定義為具有相同屬性的元素,會有些狹義)
在線性表中數據元素之間的關系是一對一的關系,即除了第一個和最后一個數據元素之外,其它數據元素都是首尾相接的(注意,這句話只適用大部分線性表,而不是全部。比如,循環鏈表邏輯層次上也是一種線性表(存儲層次上屬於鏈式存儲),但是把最后一個數據元素的尾指針指向了首位結點)[百度百科]
怎么說呢,畢竟數據結構畢竟是邏輯結構,邏輯上符合線性結構的特征即可,存儲結構能實現就行。線性表的很重要!很重要!很重要!后面的棧,隊列,串等都是基於線性表的基礎上實現的,所以說一定要學好線性表
2、線性表的特點:
對於任意的的非空線性表或者線性結構有:
1、存在唯一一個被稱為 ”第一個“的元素
2、存在唯一一個被稱為 ”最后一個“的元素
3、出第一個元素之外,每一個元素都存在一個后繼
4、除最后一個元素之外,每一個元素都存在一個前驅
3、基本操作
1、Create(*L)創建空表
2、InitEmpty(*L)初始化
3、getLength(*L)獲取長度
4、Insert(*L)插入元素
5、Remove(*L)移除元素
6、IsEmpty(*L)空表檢測
7、IsFulled(*L)表滿檢測(順序表常用,鏈式表基本不用)
8、Delete(*L)刪除表
9、getElemt(*L)獲取元素
10、Traverse(*L)遍歷輸出所有元素
11、Clear(*L)清除所有元素
4 、實現
好了最麻煩的事情開始了,數據結構在計算機上的的映射。眾所周知,線性表有兩種實現方法,一種是順序表,另一種是鏈式表,這兩種結構實現最大的不同在於前者邏輯關系無需存儲空間,而后者則需要用額外的空間(順便記錄一下,指針大小只由環境有關(嚴格意義上說和CPU的位數有關)本篇只實現順序結構)。
1、順序表:
先說一下概念:用一組地址連續的存儲單元依次存儲線性表的數據元素,這種存儲結構的線性表稱為順序表。
很明顯對於順序表來說:邏輯上相鄰的數據元素,物理次序也是相鄰的。
特點:
對表中任意元素訪問時間復雜度都為常數級。
尾部插入元素時間復雜度也為常量級。
下面是重點!!!
說明一下,講解的時候實現語言為C,后續會貼上C++和Python
實現:
敲~~~鍵盤,敲~~鍵盤!敲~~~鍵盤,敲~~鍵盤!
結構定義:
1 #define YWZLIST_INIT_SIZE 8 2 #define INC_SIZE 3 //空間增量的大小 3 4 typedef int ElemType; 5 typedef struct listnode 6 { 7 ElemType *base; 8 int capacity; //順序表容量 9 int size; //表的大小 10 } YWZlist;
函數聲明:
1 bool IsEmpty(YWZlist *list); //空表檢測 2 bool IsFull(YWZlist *list); //表滿檢測 3 bool Inc(YWZlist *list); //增加順序表的容量 4 void InitSeqlist(YWZlist *list); //初始化順序表 5 void Push_back(YWZlist *list, ElemType x); //在順序表的末尾插入元素 6 void Push_front(YWZlist *list, ElemType x); //在順序表的頭部插入元素 7 void Show_list(YWZlist *list); //顯示順序表中的元素 8 void Pop_back(YWZlist *list); //刪除順序表最后一個元素 9 void Pop_front(YWZlist *list); //刪除順序表第一個元素 10 void Insert_pos(YWZlist *list, int pos, ElemType x); //在順序表的選定位置上插入數據 11 int Find(YWZlist *list, ElemType key); //在順序表中查找元素key的下標 12 int Length(YWZlist *list); //求順序表的長度 13 void Delete_pos(YWZlist *list, int pos); //刪除順序表中特定位置的數據元素 14 void Delete_val(YWZlist *list, int key); //刪除順序表中值為key的數據元素 15 void Sort(YWZlist *list); //冒泡排序 16 void Reverse(YWZlist *list); //逆置順序列表 17 void Clear(YWZlist *list); //清除順序表中的所有元素 18 void Destroy(YWZlist *list); //摧毀順序表 19 void Merge(YWZlist *lt, YWZlist *la, YWZlist *lb); //合並兩個順序列表
上面一共19個函數,空表和表滿函數就不寫了
1、初始化:
思路很明確,先開辟一個最小的數組空間,給capacity賦值

void InitYWZlist(YWZlist *list) //初始化順序表 { //開辟初始空間,開辟失敗返回空 list->base = (ElemType *)malloc(sizeof(ElemType) * YWZLIST_INIT_SIZE); if (list->base == NULL) { printf("內存不足!\n"); return; } list->capacity = YWZLIST_INIT_SIZE - 1; list->size = -1; }
2、擴容:
順序表擴容不比鏈表,順序表需要重新開劈空間需要用到realloc函數,來看一下函數原型
_CRTIMP void* __cdecl __MINGW_NOTHROW realloc (void*, size_t); //傳進去的是要改變大小的指針
嗯,兩個參數,一個指針,一個大小,由於要新開辟一塊空間,所以我們不能用原指針來指向新開辟的空間,我i們需要一個新的之指針,下一步當然是計算需要多少空間,這就和malloc函數一樣了,自己琢磨琢磨,記得改變capacity的值,貼代碼:

bool Inc(YWZlist *list) { //重新分配內存空間 ElemType *newspace = (ElemType *)realloc(list, sizeof(ElemType) * (list->capacity + INC_SIZE)); if (newspace == NULL) { printf("內存空間已滿,無法再分配內存空間!\n"); return false; } list->base = newspace; list->capacity += INC_SIZE; return true; }
3、尾部插入:
順序表的尾部插入無比的簡單,因為物理上相鄰,所以訪問最后一個元素的時間復雜度為常量級。插入元素我們不僅需要考慮表是否表滿,同時我們還需要檢測是否可以開辟新的空間。只有當表滿且內存不足才能返回異常。

void Push_back(YWZlist *list, ElemType x) { //當表滿且無法擴容 if (IsFull(list) && !Inc(list)) { printf("順序表容量已滿,無法再在表尾繼續插入新元素!\n"); return; } list->base[list->size] = x; list->size++; }
4、頭部插入
這個就麻煩了,因為物理相鄰,所以我們得把所有元素都向后移。(這也是鏈式表出現的原因之一)當然啦,打印錯誤的條件和尾部插入相同。

void Push_front(YWZlist *list, ElemType x) { if (IsFull(list) && !Inc(list)) { printf("順序表容量已滿,無法再在表尾繼續插入新元素!\n"); return; } for (int i = list->size; i > 0; i--) { list->base[i] = list->base[i - 1]; } list->base[0] = x; list->size++; }
5、第i個索引插入
說完尾部和頭部,我們來類推一下第i個元素刪除的。
第一步,都不用想肯定是先找到第i個元素,
第二步,將n-i+1個元素個元素向后移動,
第三步,插入元素
第四步,size++
按着這個思路貼上代碼

void Insert_pos(YWZlist *list, int pos, ElemType x) { if (pos < 0 || pos > list->size) { printf("插入位置不合法,無法插入元素!\n"); return; } if (list->size >= list->capacity && !Inc(list)) { printf("順序表容量已滿,無法在插入新的元素!\n"); return; } for (int i = list->size; i > pos; i--) { list->base[i] = list->base[i - 1]; } list->base[pos] = x; list->size++; }
6、遍歷
這個直接貼代碼,太簡單了,不做作說明

void Show_list(YWZlist *list) { for (int i = 0; i < list->size; i++) { printf("%d ", list->base[i]); } printf("\n"); }
7、尾部刪除
刪除其實沒有想象中的那么難,就是把size--,因為你下次插入就是直接覆蓋了,和原先的值沒有任何關系。當然你得先判斷一下表是否為空

void Pop_back(YWZlist *list) { if (list->size == 0) { printf("順序表已空,無法再在表尾刪除元素!\n"); return; } list->size--; }
8、頭部刪除
個人覺得無論是鏈表還是順序表,只要會了插入,刪除類推一下就會了。打個比方順序表的頭部插入是,一個個往后推,那么刪除就是一個個往前移。當然一個是判斷表滿一個是判斷表空

void Pop_front(YWZlist *list) { if (list->size == 0) { printf("順序表已空,無法再在表頭刪除元素!\n"); return; } for (int i = 0; i < list->size - 1; i++) { list->base[i] = list->base[i + 1]; } list->size--; }
9、指定位置刪除
類推吧,
第一步,找到第i個元素
第二部,將從第i+1到第n個元素向前移動1
第三步,size--;
是不是和插入很像,所以有些代碼是很像的

void Delete_pos(YWZlist *list, int pos) { if (pos < 0 || pos >= list->size) { printf("刪除位置不合法,無法刪除元素!\n"); return; } for (int i = pos; i < list->size - 1; i++) { list->base[i] = list->base[i + 1]; } list->size--; }
10、按元素刪除
這個是我覺得,一般人找東西,都只記得叫什么名字而不是第幾個,所以寫了一個按元素刪除的。其實思路和指定位置刪除一樣找到最前端的一樣的元素后插入即可(有bug,當表中存在重復元素的時候,只會添加到最前面那個)

void Delete_val(YWZlist *list, int key) //刪除順序表中值為key的數據元素 { int pos = Find(list, key); if (pos == -1) { printf("順序表中沒有這個元素!\n"); return; } Delete_pos(list, pos); }
11、元素查找
不打算說,貼代碼

int Find(YWZlist *list, ElemType key) //在順序表中查找元素key的下標 { for (int i = 0; i < list->size; i++) { if (list->base[i] == key) return i; } return -1; }
12、返回長度
size中記錄了表長,直接return就好了

int Length(YWZlist *list) { return list->size; }
13、排序
冒泡,不在這里做說明,自己去看。其實可以直接調用qsort函數的寫一個compare函數就行了

void Sort(YWZlist *list) { for (int i = 0; i < list->size - 1; i++) { //排序的趟數(例如5個數據需要比較4趟) for (int j = 0; j < list->size - 1 - i; j++) { //每一趟比較中的比較次數(例如5個數據在第0趟需要比較4次) if (list->base[j] > list->base[j + 1]) { ElemType temp = list->base[j]; list->base[j] = list->base[j + 1]; list->base[j + 1] = temp; } } } }
14、順序表逆序
要么寫個for二分表只執行到n/2,要么寫個while,大小指針(不是真正意義上的指針)小指針大於大指針的時候停止

void Reverse(YWZlist *list) //逆置順序列表 { if (list->size == 0 || list->size == 1) { return; } int low = 0, high = list->size - 1; while (low < high) { ElemType temp = list->base[low]; list->base[low] = list->base[high]; list->base[high] = temp; low++; high--; } }
15、清空表
無比簡單,就一句size = -1;

void Clear(YWZlist *list) { list->size = 0; }
16、刪除表
free完畢之后,將所有成員重置

void Destroy(YWZlist *list) { free(list->base); list->base = NULL; list->capacity = 0; list->size = 0; }
17、合並兩個順序表
這個就比較麻煩了
第一步:開辟足夠的空間
第二步:讓La與Lb比較,誰大/小存誰,直到一個表結束
第三步:將余下的元素都,存入表Lc中。
第四步:計算表長。

void Merge(YWZlist *lt, YWZlist *la, YWZlist *lb) { lt->capacity = la->size + lb->size; lt->base = (ElemType *)malloc(sizeof(ElemType) * lt->capacity); //assert(lt->base != NULL); int ia = 0, ib = 0, ic = 0; while (ia < la->size && ib < lb->size) { if (la->base[ia] < lb->base[ib]) { lt->base[ic++] = la->base[ia++]; } else { lt->base[ic++] = lb->base[ib++]; } } while (ia < la->size) { lt->base[ic++] = la->base[ia++]; } while (ib < lb->size) { lt->base[ic++] = lb->base[ib++]; } lt->size = la->size + lb->size; Show_list(lt); }
本篇只實現順序表