線性表
線性表由具有相同數據類型的n個元素構成,這些元素之間存在一一對應的線性關系。其中n為表長,當n=0的時候線性表是一個空表。簡單來說,線性表中的元素排列成一條線,每個元素最多有一個直接的前驅和后繼(除第一個和最后一個元素外)。線性表的第一個數據元素稱為表頭元素,最后一個數據元素稱為表尾元素。
線性表的特點
- 表中元素的個數有限。
- 表中元素具有邏輯上的順序,有先后次序。
- 表中元素都是數據元素,每個元素都是單獨的基本單位。
- 表中元素的數據類型相同,占用相同大小的存儲空間。
- 表中元素具有抽象性,表示邏輯上的數據對象。
- 表中每個元素除首末元素外,有且僅有一個直接前驅和一個直接后繼,保證線性關系。
- 線性表可用順序存儲(如數組)或鏈式存儲(如鏈表)兩種方式實現。
- 支持按位置訪問元素,比如通過索引或指針遍歷。
- 線性表的邏輯結構獨立于物理存儲,實現方式多樣。
線性表的基本操作
InitList(&L); // 初始化線性表
Length(L); // 求表長。返回表L的長度,即L中數據元素的個數
LocateElem(L, e); // 按值查找操作,即查找表中具有給定e的元素
GetElem(L, i); // 按位查找操作,獲取表L中第i個位置的元素的值
ListInsert(&L, i, e); // 將元素e插入表L的第i個位置
ListDelete(&L, i, &e); // 刪除表L第i個位置的元素,并用e返回刪除元素的值
PrintList(L); // 按先后順序打印出表L的所有元素值
Empty(L); // 判斷表L是否為空,為空返回true,否則返回false
DestyoyList(&L); // 銷毀線性表
線性表的順序表示:順序表
線性表的順序存儲也稱順序表。
順序表是一種線性表的存儲結構,也叫做“順序存儲結構”或“順序結構”。它將線性表中的元素按順序存放在一塊連續的存儲空間中(通常是數組),每個元素占用相同大小的存儲單元。通過元素的下標,可以快速訪問和定位元素。簡單來說,順序表就是利用數組實現的線性表。
由于每個數據元素的存儲位置都和順序表的起始位置相差一個和該數據元素的位序乘正比的常數,因此順序表的任意數據元素都支持隨機存取,所以線性表的順序存儲結構是一種隨機存取的存儲結構。
隨機存取:只要一種數據結構能夠在常數時間 O(1) 內直接找到任意元素的地址,就可以稱之為支持隨機存取。支持隨機存取的數據結構具有以下特征:
-
直接訪問:能夠直接訪問任何元素而不需要依賴于順序遍歷。例如,通過索引或其他唯一標識符(如鍵)可以直接獲取到數據。
-
時間復雜度:支持隨機存取的關鍵是,在訪問任何元素時,不論元素在數據結構中的位置,都能保持常數時間的訪問復雜度 O(1)。這意味著,無論數據的大小如何,訪問時間始終是固定的。
靜態分配內存空間的順序表實例
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>#define MaxSize 100
// 描述一個順序表的存儲結構
typedef struct
{unsigned char data[MaxSize];unsigned int length;
} SqList;// 順序表初始化
void InitList(SqList *L)
{L->length = 0;
}// 求表長
int Length(SqList L)
{return L.length;
}// 按值查找操作,即查找表中具有給定值e的元素
int LocateElem(SqList L, unsigned char e)
{for (int i = 0; i < L.length; i++){if (L.data[i] == e)return i + 1;}return 0;
}// 按位查找操作,獲取表L中第i個位置的元素的值
int GetElem(SqList L, unsigned int i)
{if (i < 1 || i > L.length)return 0;elsereturn (L.data[i - 1]);
}// 將元素e插入表L的第i個位置
bool ListInsert(SqList *L, unsigned int i, unsigned char e)
{if (i < 1 || i > L->length + 1)return false;if (L->length >= MaxSize)return false;// 搬運元素的時候注意要從后往前開始搬運,從前往后會導致前面的數據元素把后面的元素覆蓋掉for (int j = L->length; j >= i; j--)L->data[j] = L->data[j - 1];L->data[i - 1] = e;L->length++;return true;
}// 刪除表L第i個位置的元素,并用e返回刪除元素的值
bool ListDeleteSqList(SqList *L, unsigned int i, unsigned char *e)
{if (i < 1 || i > L->length + 1)return false;if (L->length >= MaxSize)return false;*e = L->data[i-1];for (int j = i; j < L->length; j++)L->data[j-1] = L->data[j];L->length--;return true;
}// 按先后順序打印出表L的所有元素值
void PrintList(SqList *L)
{for (int i = 0; i < L->length; i++){printf("Array index:%d, data:%d\r\n", i, L->data[i]);}
}// 判斷表L是否為空,為空返回true,否則返回false
bool Empty(SqList L)
{if (L.length == 0)return true;elsereturn false;
}// 銷毀順序表
void DestyoyList(SqList *L)
{L->length = 0;L = NULL;
}
線性表的鏈式表示:鏈表
順序表雖然具有隨機存取的優點,但是進行刪除和插入的操作時需要移動大量的元素,并且需要一段連續的內存空間進行存儲。而鏈式存儲的線性表,不需要使用連續的一段內存空間來存儲整個線性表,它是利用 “鏈” 建立元素之間的邏輯關系,因此插入和刪除操作不需要移動元素,只需要修改指針,但這樣也就失去了順序表隨機存取的優點。
鏈表是一種線性表的鏈式存儲結構。每個節點包含數據部分和指針部分,指針指向下一個節點的位置,這樣節點按邏輯順序連接起來,形成一個線性結構。通過指針的連接,可以方便地進行插入和刪除操作,不需要像順序表那樣移動大量元素。
- 單鏈表:
- 除尾節點外,每個節點只有一個指針,指向下一個節點。
- 尾節點的指針指向
NULL
,表示鏈表結束。
- 雙鏈表:
- 除頭尾節點外,每個節點有兩個指針,分別指向上一個節點和下一個節點。
- 頭節點的前驅指針指向
NULL
,后繼指針指向下一個節點。 - 尾節點的后繼指針指向
NULL
,前驅指針指向上一個節點。
- 循環單鏈表:
- 每個節點都有一個指針指向下一個節點。
- 尾節點指針不指向
NULL
,而是指向頭節點,形成環狀結構。
- 循環雙鏈表:
- 每個節點有兩個指針,分別指向上一個節點和下一個節點。
- 頭節點的前驅指針指向尾節點,尾節點的后繼指針指向頭節點,形成雙向環狀結構。
動態分配內存空間的單鏈表實例
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>typedef struct Node
{int data;struct Node *next;
} Node, *Linklist;/*** 方法1:初始化鏈表頭指針(通過入口參數傳入指針的地址)* @param L 指向鏈表頭指針的指針(二級指針)* @return true 分配成功,false 分配失敗** 說明:* - 函數接收一個指向頭指針變量的指針(Linklist *L),* 可以在函數內部修改調用者的頭指針變量,使其指向新節點。* - malloc 分配一塊內存空間存儲一個 Node 結構體。* - 如果分配失敗,返回 false。* - 分配成功后,設置頭節點的數據成員和指針域。* - 這種寫法充分利用了 C 語言的值傳遞特性,通過傳入指針的地址,* 修改指針本身,實現頭指針的初始化。*/
bool init_Linklist(Linklist *L)
{*L = malloc(sizeof(Node));if (*L == NULL){return false;}(*L)->data = 1;(*L)->next = NULL;return true;
}/*** 方法2:初始化鏈表頭指針(通過函數返回新分配的節點指針)* @return 返回指向鏈表頭節點的指針,分配失敗返回 NULL** 說明:* - 函數內部通過 malloc 分配一個 Node 節點。* - 給新節點賦予初始數據和指針值。* - 函數直接返回新分配節點的指針。* - 調用者通過接收返回值獲得鏈表頭指針。* - 這種寫法簡潔,調用方便,適用于不需要返回其它狀態碼,* 只需要通過 NULL 判斷失敗的情況。*/
Linklist INIT2_Linklist(void)
{Linklist L = malloc(sizeof(Node));L->data = 1;L->next = NULL;return L;
}// 在頭節點后面插入新的節點
bool insert_below_head(Linklist *head, int new_data)
{Node *new_node = malloc(sizeof(Node));if (new_node == NULL)return false;new_node->data = new_data;new_node->next = (*head)->next;(*head)->next = new_node;return true;
}// 在頭節點前面插入新的節點
bool insert_front_head(Linklist *head, int new_data)
{// 創建新節點Node *new_node = malloc(sizeof(Node));if (new_node == NULL)return false;new_node->data = new_data;// 插入新節點new_node->next = (*head);// 更新頭指針為新的節點*head = new_node;return true;
}// 在尾節點后面插入新的節點
bool insert_tail(Linklist *head, int new_data)
{Node *new_node = malloc(sizeof(Node));if(new_node == NULL)return false;new_node->data = new_data;new_node->next = NULL; // 尾節點的next為NULLNode *node = *head;// 遍歷找到最后一個節點,循環退出時node為尾節點while(node->next != NULL){node = node->next;}node->next = new_node;return true;
}// 在尾節點前面插入新的節點
bool insert_front_tail(Linklist *head, int new_data, int length)
{Node *new_node = malloc(sizeof(Node));if(new_node == NULL)return false;new_node->data = new_data;new_node->next = NULL; // 尾節點的next為NULLNode *node = *head;while (node->next->next != NULL){node = node->next;}new_node->next = node->next;node->next = new_node;return true;
}// 打印鏈表所有節點
void print_node(Linklist *head)
{Node *new_node = *head;if (*head == NULL){printf("鏈表為空\n");return;}// 遍歷打印出所有節點,循環退出時new_node為NULLwhile (new_node != NULL){printf("node:%p,DATA:%d\r\n", new_node, new_node->data);new_node = new_node->next;}
}// 刪除鏈表
void delete_list(Linklist *head)
{while((*head) != NULL){Node *node = *head;*head = (*head)->next;free(node);}
}// 求鏈表長度
int getlistlength(Linklist *head)
{if (*head == NULL){printf("鏈表長度為:0\n");return 0;}Node *node = *head;int i = 0;while(node->next != NULL){i++;node = node->next;}printf("鏈表長度為:%d (不包含頭節點)\n",i);return i;
}// 按值查找某個節點,返回該節點的位號
int seek_node_by_value(Linklist *head, int value)
{Node *current = *head;int i = 0;while (current != NULL && current->data != value){current = current->next;i++;}printf("[check by value] index:%d,DATA:%d\r\n",i,current->data);return i;
}// 按位置查找某個節點,返回該節點的值,eg:查找第3個節點的值
int seek_node_by_index(Linklist *head, int index)
{Node *current = *head;int i = 0;while (current != NULL && i < index - 1){current = current->next;i++;}printf("[check by index] index:%d,DATA:%d\r\n",i,current->data);return current->data;
}// 將某個節點插入第i個位置(不考慮頭尾需要的額外處理)
bool insert_node_by_index(Linklist *head, int index, int data)
{Node *new_node = malloc(sizeof(Node));if(new_node == NULL)return false; new_node->data = data;Node *current_node = *head; int i = 0;// while (i < index - 1) 這個條件正是為了讓循環結束時 current_node 指向插入位置的前驅節點,方便在這個位置插入新節點。while(current_node != NULL && i < index - 1){current_node = current_node->next;i++;}new_node->next = current_node->next;current_node->next = new_node;return true;
}// 刪除表中第i個節點
bool delete_node_by_index(Linklist *head, int index)
{Node *current = *head;int i = 0;while(current != NULL && i < index - 1){current = current->next;i++;}// 先保存待刪除的節點指針Node *node_to_delete = current->next;// 修改指針來斷開鏈表中的該節點current->next = current->next->next;// 最后釋放內存free(node_to_delete);return true;
}
順序表和鏈表的比較
-
存取方式
- 順序表:采用順序存取,通過數組的下標可以直接訪問任意位置的元素,訪問速度快(時間復雜度為O(1))。
- 鏈表:采用鏈式存取,訪問元素時需從表頭開始沿指針逐個訪問,訪問特定位置元素時間復雜度為O(n)。
-
邏輯結構和物理結構
-
順序表
邏輯結構是線性表,元素按順序排列。物理結構是連續的內存空間(數組)。
-
鏈表
邏輯結構也是線性表,元素按順序連接。物理結構是分散的節點,節點通過指針連接,內存地址不必連續。
-
-
查找、插入和刪除操作
操作 | 順序表 | 鏈表 |
---|---|---|
查找 | 支持隨機訪問,時間O(1) | 需順序遍歷,時間O(n) |
插入 | 插入位置后元素需移動,時間O(n) | 插入位置通過指針調整,時間O(1)(已找到插入點) |
刪除 | 刪除后元素需移動,時間O(n) | 通過指針調整刪除,時間O(1)(已找到刪除點) |
-
空間分配
-
順序表:需要預先分配一塊連續的固定大小的內存空間,可能造成浪費或空間不夠。
-
鏈表:動態分配內存,根據需要申請和釋放節點空間,節省空間但需要額外指針的存儲開銷。
-