資料結構(1)—— 線性表
阿新 • • 發佈:2021-07-09
寫在前面
為了考研,需要複習資料結構。而對於資料結構這門學科來說,寫程式碼是非常必要的。用程式碼把一些常見的資料結構與演算法實現一遍,非常有利於對於資料結構的理解。
於是今天是第一章,即線性表的實現。主要參考是王道的資料結構複習指導
與黑皮的嚴蔚敏的教材
。雖然程式碼檔案字尾都是cpp,但寫法還是C的寫法,主要借用了cpp裡的&
與bool
來方便書寫。
靜態儲存的順序表實現
注意點
靜態儲存的順序表,其實就是用陣列來實現,主要注意點在於陣列的下標和順序表的位序並不是一一對應的關係,而是差1(順序表的位序 = 陣列的下標 + 1)。要注意這點。
程式碼
/* * @Description: 靜態儲存的順序表實現 * @version: 1.0 * @Author: Liuge * @Date: 2021-07-05 21:39:06 */ #include<bits/stdc++.h> #define maxSize 10 // 定義靜態儲存的順序表結構 typedef struct{ int data[maxSize]; int length; }SqList; // 初始化 void initList(SqList &L){ // 將所有初值設定為0 for(int i = 0;i < maxSize;i++){ L.data[i] = 0; } // 長度設定為0 L.length = 0; } // 插入 bool listInsert(SqList &L,int i,int e){ // 判斷i的範圍是否有效 if(i < 1 || i>L.length+1){ return false; } // 判斷儲存空間是否已經滿 if(L.length >= maxSize){ return false; } // 將第i個元素之後的元素後移 for(int j=L.length;j >= i;j--){ L.data[j] = L.data[j-1]; } // 在位置i處放入e L.data[i-1] = e; L.length++; return true; } // 列印順序表 void printList(SqList &L){ printf("順序表的元素為:\n"); for(int i = 0;i < L.length; i++){ printf("%d ",L.data[i]); } printf("\n"); } // 刪除 bool listDelete(SqList &L,int i,int &e){ // 判斷i的範圍 if(i < 1 || i > L.length){ return false; } // 將被刪除的元素賦值給e e = L.data[i-1]; // 將第i個位置後的元素前移 for(int j=i;j<L.length;j++){ L.data[j-1] = L.data[j]; } L.length--; return true; } // 按值查詢 int locateElem(SqList L,int e){ int i; for(i = 0;i < L.length;i++){ if(L.data[i] == e){ return i+1; } } return 0; } // 按位序查詢 int getElem(SqList L,int i){ return L.data[i-1]; } // 主程式測試 int main(){ SqList L; // 初始化 initList(L); printList(L); // 插入幾個元素看看 listInsert(L,1,1); listInsert(L,2,2); listInsert(L,3,22); printList(L); // 刪除一個元素 int deleteElem = 0; listDelete(L,1,deleteElem); printList(L); // 查詢元素22 printf("元素22的所在位置為 -> %d\n",locateElem(L,22)); // 根據位序查詢 printf("位序為1的位置為 -> %d\n",getElem(L,1)); return 0; }
動態儲存的順序表實現
注意點
動態儲存的順序表,相當於是在結構體中儲存一個指標,當容量不夠的時候再去動態申請一波新的空間,把舊的移到新區域就好。其他實現與靜態儲存的順序表差別不大。
程式碼
/* * @Description: 動態儲存的順序表實現 * @version: 1.0 * @Author: Liuge * @Date: 2021-07-06 19:18:08 */ #include<bits/stdc++.h> #define initSize 3 // 定義動態儲存的順序表結構 typedef struct{ int *data; int maxSize; int length; }SeqList; // 初始化順序表 void initList(SeqList &L){ // 用malloc函式申請一片連續的儲存空間 L.data = (int *)malloc(initSize * sizeof(int)); // 長度設為0 L.length = 0; // 最大大小設為初始大小 L.maxSize = initSize; } // 動態增加長度 void increaseSize(SeqList &L,int len){ int *p = L.data; // C // L.data = (int *) malloc((L.maxSize + len) * sizeof(int)); // C++ L.data = new int[L.maxSize + len]; // 把資料複製到新區域 for(int i = 0;i < L.length;i++){ L.data[i] = p[i]; } // 增加長度 L.maxSize += len; // 釋放原有空間 // C // free(p); // C++ delete p; } // 插入 bool listInsert(SeqList &L,int i,int e){ // 判斷i的範圍是否有效 if(i < 1 || i>L.length+1){ return false; } // 判斷儲存空間是否已經滿 if(L.length >= L.maxSize){ // 如果已滿,申請更多空間 // 一次多申請幾個 increaseSize(L,5); } // 將第i個元素之後的元素後移 for(int j=L.length;j >= i;j--){ L.data[j] = L.data[j-1]; } // 在位置i處放入e L.data[i-1] = e; L.length++; return true; } // 列印順序表 void printList(SeqList &L){ printf("順序表的元素為:\n"); for(int i = 0;i < L.length; i++){ printf("%d ",L.data[i]); } printf("\n"); } // 刪除 bool listDelete(SeqList &L,int i,int &e){ // 判斷i的範圍 if(i < 1 || i > L.length){ return false; } // 將被刪除的元素賦值給e e = L.data[i-1]; // 將第i個位置後的元素前移 for(int j=i;j<L.length;j++){ L.data[j-1] = L.data[j]; } L.length--; return true; } // 按值查詢 int locateElem(SeqList L,int e){ int i; for(i = 0;i < L.length;i++){ if(L.data[i] == e){ return i+1; } } return 0; } // 按位序查詢 int getElem(SeqList L,int i){ return L.data[i-1]; } // 主程式測試 int main(){ SeqList L; // 初始化 initList(L); printList(L); // 插入幾個元素看看 listInsert(L,1,1); listInsert(L,2,2); listInsert(L,3,22); printList(L); // 此時已滿,測試能不能繼續插入 listInsert(L,2,30); printList(L); // 刪除一個元素 int deleteElem = 0; listDelete(L,1,deleteElem); printList(L); // 查詢元素30 printf("元素30的所在位置為 -> %d\n",locateElem(L,30)); // 根據位序查詢 printf("位序為1的位置為 -> %d\n",getElem(L,1)); return 0; }
帶頭結點的單鏈表實現
注意點
這裡實現的是帶頭結點的單鏈表,不帶頭結點的會在各個函式上有著區別,要注意這點。對於有頭結點的單鏈表,需要注意判空條件為L -> next == NULL
,即頭指標指向NULL,否則都為非空。其他需要注意的便是頭插法與尾插法的實現了,這塊為難點和重點。
程式碼
/* * @Description: 單鏈表的實現 * @version: 1.0 * @Author: Liuge * @Date: 2021-07-06 20:57:47 */ #include<bits/stdc++.h> // 定義帶頭結點的單鏈表 typedef struct LNode{ int data; struct LNode *next; }LNode,*LinkList; // 初始化單鏈表 bool initList(LinkList &L){ // 分配一個頭結點 L = (LNode *) malloc(sizeof(LNode)); // 記憶體已滿 if (L == NULL){ return false; } // 把頭結點指向空 L -> next = NULL; return true; } // 判斷單鏈表是否為空 bool empty(LinkList L){ if(L -> next == NULL){ return true; } return false; } // 連結串列輸出 void printList(LinkList L){ LNode *p = L -> next; printf("單鏈表內的元素為:\n"); while(!empty(p)){ printf("%d ",p->data); p = p->next; } // 最後一個元素雖指向NULL但仍有值,繼續列印 printf("%d ",p->data); printf("\n"); } // 按位查詢,返回第i個元素 LNode * getElem(LinkList L,int i){ if(i < 0){ return NULL; } LNode *p; int j = 0; p = L; while(p != NULL && j < i){ p = p -> next; j++; } return p; } // 按值查詢 LNode * locateElem(LinkList L,int e){ LNode *p = L -> next; // 迴圈遍歷連結串列 while(p != NULL && p->data !=e){ p = p->next; } return p; } // 後插,在p結點之後插入元素e bool insertNextNode(LNode *p,int e){ // 判斷是否超出長度 if(p == NULL){ return false; } LNode *s = (LNode *)malloc(sizeof(LNode)); if(s == NULL){ return false; } s->data = e; s->next = p->next; p->next = s; return true; } // 前插,在p結點之前插入元素e,O(1) bool insertPriorNode(LNode *p,int e){ if(p == NULL){ return false; } LNode *s = (LNode *) malloc(sizeof(LNode)); if(s == NULL){ return false; } // 相當於把p的值給了s,把新的值e給了p,然後連線起來 s->next = p->next; p->next = s; s->data = p->data; p->data = e; return true; } // 插入 bool listInsert(LinkList &L,int i,int e){ if(i < 1){ return false; } // 找到第i-1個結點 LNode *p = getElem(L,i-1); // p結點之後插入元素e return insertNextNode(p,e); } // 刪除 O(n) bool listDelete1(LinkList &L,int i,int &e){ if(i < 1){ return false; } // 查詢前驅結點 LNode *p = getElem(L,i-1); // q指向被刪除結點 LNode *q = p->next; // 把q從鏈中斷開 p ->next = q->next; // 釋放空間 free(q); return true; } // 刪除 O(1) // !有坑,在刪除最後一個結點時會報錯 bool listDelete2(LNode *p){ if (p == NULL){ return false; } LNode *q = p->next; p->data = p->next->data; p->next = q->next; free(q); return true; } // 求連結串列長 int getLength(LinkList L){ int len = 0; LNode *p = L; while(p -> next != NULL){ p = p->next; len++; } return len; } // 尾插法建立 LinkList listTailInsert(LinkList &L){ int x; // 初始化 initList(L); // 定義頭尾指標 LNode *s = L; LNode *r = L; scanf("%d",&x); while(x != 9999){ // 在r結點之後插入x s = (LNode *) malloc(sizeof(LNode)); s -> data = x; r -> next = s; // 讓r指標指向新的尾 r = s; scanf("%d",&x); } r -> next = NULL; return L; } // 頭插法建立 LinkList listHeadInsert(LinkList &L){ LNode *s; int x; // 初始化L initList(L); scanf("%d",&x); while(x != 9999){ // 把新結點插入到頭部 s = (LNode *)malloc(sizeof(LNode)); s->data = x; s->next = L->next; L->next = s; scanf("%d",&x); } return L; } // 主函式測試 int main(){ // 測試尾插法 LinkList L1,L2; listTailInsert(L1); printList(L1); // 測試頭插法 listHeadInsert(L2); printList(L2); // 測試插入一個新結點 listInsert(L1,1,100); listInsert(L2,3,200); printList(L1); printList(L2); // 測試刪除一個結點 int deletedNode1; listDelete1(L1,3,deletedNode1); LNode *deletedNode2 = getElem(L2,3); listDelete2(deletedNode2); printList(L1); printList(L2); // 測試查詢結點 printf("在L1中查詢100,下一個位置的值為 -> %d",locateElem(L1,100)->next->data); return 0; }
雙向連結串列的實現
注意點
對於雙向連結串列,與單鏈表不同的是多了一個前向指標,可以實現向前遍歷這樣的操作了。需要注意的是雙向連結串列的判空條件以及插入時的臨界條件。
程式碼
/*
* @Description: 雙向連結串列的實現
* @version: 1.0
* @Author: Liuge
* @Date: 2021-07-07 21:32:35
*/
#include <bits/stdc++.h>
// 定義雙向連結串列結構
typedef struct DLinkNode
{
int data;
// 前向指標與後向指標
DLinkNode *pre;
DLinkNode *next;
} DLinkNode, *DLinkList;
// 初始化雙鏈表
bool initList(DLinkList &DL)
{
// 分配一個頭結點
DL = (DLinkNode *)malloc(sizeof(DLinkNode));
// 記憶體已滿
if (DL == NULL)
{
return false;
}
// 把頭結點指向空
DL->pre = NULL;
return true;
}
// 建立
DLinkList createDLinkList(DLinkList &DL)
{
int x;
initList(DL);
DLinkNode *p;
DLinkNode *s;
p = DL;
scanf("%d", &x);
while (x != 9999)
{
s = (DLinkNode *)malloc(sizeof(DLinkNode));
s->data = x;
p->next = s;
s->pre = p;
p = s;
scanf("%d", &x);
}
s->next = NULL;
return DL;
}
// 列印
void printList(DLinkList DL)
{
DLinkNode *p = DL->next;
printf("雙鏈表內的元素為:\n");
while (p->next)
{
printf("%d ", p->data);
p = p->next;
}
// 最後一個元素雖指向NULL但仍有值,繼續列印
printf("%d ", p->data);
printf("\n");
}
// 頭部插入
void insertListHead(DLinkList &DL, int data)
{
DLinkNode *pNode = (DLinkNode *)malloc(sizeof(DLinkNode));
pNode->data = data;
pNode->next = DL->next;
pNode->pre = DL;
if (pNode->next != NULL)
{
pNode->next->pre = pNode;
}
DL->next = pNode;
}
// 尾部插入
void insertListTail(DLinkList &DL, int data)
{
DLinkNode *pNode = (DLinkNode *)malloc(sizeof(DLinkNode));
pNode->data = data;
DLinkNode *pCur = DL;
while (pCur->next != NULL)
{
pCur = pCur->next;
}
pCur->next = pNode;
pNode->pre = pCur;
pNode->next = NULL;
}
// 獲取連結串列長度
int getListLength(DLinkList DL)
{
DLinkNode *p;
int len = 0;
p = DL;
for (; p->next != NULL; p = p->next)
{
len++;
}
return len;
}
// 指定位置插入節點,前插
bool insertList(DLinkList &DL, int data, int index)
{
DLinkNode *pCur = DL;
int len = getListLength(DL);
if (index > len)
{
return false;
}
else if (index == 0)
{
insertListHead(DL, data);
return true;
}
else if (index == len)
{
insertListTail(DL, data);
return true;
}
DLinkNode *pNode = (DLinkNode *)malloc(sizeof(DLinkNode));
pNode->data = data;
int i = 0;
while (index--){
pCur = pCur->next;
}
pNode->next = pCur->next;
pCur->next = pNode;
pNode->pre = pCur;
pNode->next->pre = pNode;
return true;
}
// 刪除指定值
bool deleteListNode(DLinkList &DL,int key){
DLinkNode *pPre = DL;
DLinkNode *pCur = DL->next;
while(pCur != NULL){
if(pCur->data == key){
if(pCur->next != NULL){
pCur->next->pre = pCur->pre;
}
pCur->pre->next = pCur->next;
pPre = pCur;
pCur = pCur->next;
free(pPre);
return true;
}else{
pPre = pCur;
pCur = pCur->next;
}
}
return false;
}
// 主函式測試
int main(){
DLinkList DL;
// 建立
createDLinkList(DL);
printList(DL);
// 從頭插入一個值
insertListHead(DL,5);
printList(DL);
// 從尾插入一個值
insertListTail(DL,100);
printList(DL);
// 任意位置插入
insertList(DL,1000,2);
printList(DL);
// 刪除值為100的結點
deleteListNode(DL,100);
printList(DL);
}
帶頭結點的單迴圈連結串列的實現
注意點
這裡實現的是帶頭結點的單迴圈連結串列,迴圈連結串列在理解了其本質後其實是很好寫的。主要就是把最後一個元素的next
指標指向了第一個元素。關於實現,需要注意迴圈連結串列的判空條件,以及插入時的操作。
程式碼
/*
* @Description: 迴圈單鏈表的實現
* @version: 1.0
* @Author: Liuge
* @Date: 2021-07-08 19:38:04
*/
#include<bits/stdc++.h>
// 定義迴圈單鏈表結構體
typedef struct CLNode{
int data;
CLNode *next;
}CLNode,* CLinklist;
// 初始化
bool initList(CLinklist &CL){
CL = (CLNode *)malloc(sizeof(CLNode));
if(CL == NULL){
return false;
}
// 迴圈連結串列初始化頭結點指向本身
CL->next=CL;
return true;
}
// 判空
bool isEmpty(CLinklist CL){
// 指向本身即為空
if(CL->next == CL){
return true;
}
return false;
}
// 長度
int getLength(CLinklist CL){
CLNode *p = CL->next->next;
int len = 0;
// p未到表頭時
while(p != CL->next){
len++;
p = p->next;
}
return len;
}
// 取元素
int getElem(CLinklist CL,int i,int *e){
CLNode *p = CL->next;
int j = 0;
if(i < 1 || i > getLength(CL)){
// 不合法,返回-1
return -1;
}
while(j < i){
j++;
p = p->next;
}
*e = p->data;
// 返回0代表成功
return 0;
}
// 定位元素
int locateElem(CLinklist CL,int e){
CLNode *p = CL->next->next;
int j = 0;
while(p != CL->next){
j++;
if(p->data == e){
return j;
}
p = p->next;
}
return -1;
}
// 插入元素
bool listInsert(CLinklist &CL,int i,int e){
CLNode *p = CL->next;
CLNode *s;
int j = 0;
if(i < 1 || i > getLength(CL) + 1){
return false;
}
while(j < i -1){
j++;
p = p->next;
}
s = (CLNode *)malloc(sizeof(CLNode));
s->data = e;
s->next = p->next;
p->next = s;
// 如果在表尾插入,則把新插入的元素當做頭結點(第一個元素)
if(p == CL){
CL = s;
}
return true;
}
// 刪除元素
bool listDelete(CLinklist &CL,int i,int &e){
CLNode *p = CL->next;
CLNode *q;
int j = 0;
if(i < 1 || i > getLength(CL)){
return false;
}
while(j < i - 1){
j++;
p = p->next;
}
q = p->next;
e = q->data;
p->next = q->next;
// 刪除表尾元素,指標發生變化
if(q == CL){
CL = p;
}
free(q);
return true;
}
// 列印
void printList(CLinklist CL)
{
CLNode *p = CL->next->next;
printf("迴圈連結串列內的元素為:\n");
while (p != CL->next)
{
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
// 主函式測試
int main(){
CLinklist CL;
initList(CL);
printList(CL);
// 插入幾個元素
listInsert(CL,1,100);
listInsert(CL,2,2);
listInsert(CL,3,3);
listInsert(CL,4,4);
listInsert(CL,5,5);
printList(CL);
// 插入到表尾試試
listInsert(CL,5,1000);
printList(CL);
// 刪除一個元素
int deletedElem;
listDelete(CL,3,deletedElem);
printList(CL);
}
總結
這裡還缺雙向迴圈連結串列的實現,其實這種實現也並不是很難的方式,讀者可以去自行實現。線上性表這一塊大量使用了指標,一定要好好理解指標的概念才能更好地實現程式碼。