前言
李柱明部落格:https://www.cnblogs.com/lizhuming/p/15487297.html
線性表的定義
線性表:
- 線性表(list)- 零個或多個資料元素的有限序列。
- 序列:第一個元素無前驅,最後一個元素無後繼,其他每個元素都有且只有一個前驅和後繼。
- 有限:元素的個數是有限的。
- 元素型別:元素型別相同。
線性表的資料型別&操作
線性表操作
-
線性表的建立和初始化。
-
清空線性表。
-
獲取線性表的資料元素。
- 按位置獲取。
- 按自定義參考值獲取。
-
插入資料。
-
刪除資料。
-
線性表元素個數。
-
線性表空判。
-
線性表滿判。
-
檢查元素是否存在。
資料型別定義
- 資料空間。
- 自定義資料。如鎖、長度記錄等等。
- 常用操作。
/* 線性表資料結構 */
typedef struct
{
/* data_typde Data; 資料空間。 */
/* 自定義資料。 */
/* 資料操作。 */
}
複雜操作
如合併集合 B 中的資料到集合 A 中:
/* 將所有線上性表 list_b 中但是不在 list_a 中的資料元素插入到 list_a 中 */
void list_union(list *list_a, list *list_b)
{
int i = 0;
int list_a_len = 0;
int list_b_len = 0;
elem_type elem = 0;
list_a_len = list_lenght(list_a);
list_b_len = list_lenght(list_b);
for(i = 1; i <= list_b_len; i++)
{
list_get_elem(list_b, i, &elem); /* 取 list_b 中第 i 個資料元素賦給 elem */
if(!list_locate_elem(list_a, elem) /* list_a 中不存在和 elem 相同資料元素 */
{
list_insert(list_a, ++list_a_len, elem); /* 插入 */
}
}
}
線性表的順序儲存結構
線性表的兩種物理結構:
- 順序儲存結構。
- 鏈式儲存結構。
順序儲存結構的定義
定義:
- 線性表的順序儲存結構,指用一段地址連續的儲存單元依次儲存線性表的資料元素。
順序儲存方式
若每個資料型別相同,可以用 C 語言的一維陣列來實現順序儲存結構:
#define MAX_SIZE /* 資料空間 */
typedef int elem_type; /* 元素型別 */
typedef struct
{
int length; /* 線性表長度 */
elem_type data[MAX_SIZE]; /* 實際空間 */
}list_t;
描述順序儲存結構需要三個屬性:
- 儲存空間的起始位置:陣列 data,它的儲存位置就是儲存空間的儲存位置。
- 線性表的最大儲存容量:陣列長度 MAX_SIZE。
- 線性表的當前長度:length。
資料長度和線性表長度的區別
- 資料長度:是存放線性表的儲存空間的長度,儲存分配後這個量是一般不變的。
- 線性表長度:線性表中的資料元素的個數。隨著元素的插入、刪除操作的變化而變化。
- 注意:線性表的長度總是小於等於陣列的長度。
地址的計算方法
地址:儲存器中的每個儲存單元都有自己的編號,這個編號稱為地址。(記憶體地址)
計數:線性表從 1 開始,而 c 語言中的陣列從 0 開始。
locate:獲得儲存位置的函式(假設每個資料元素佔 c 個儲存單元),參考公式:
- locate(Ai + n) = locate(Ai) + n*c;
- locate(Ai) = locate(A1) + (i-1)*c;
通過上述公式可以隨時算出線性表中任意位置的地址,且都是相同時間。
由此可知時間複雜度為 O(1)。
每個位置存、取資料,都是相等的時間,也就是一個常數。
通常把具有這一特點的儲存結構稱為 隨機存取結構。
順序儲存結構的插入與刪除
注意:
- 線性表的第 i 個數是從 1 開始計數的,而陣列小標是從 0 開始的。
- 分清應該是 i 還是 i-1。
- 注意指標丟失問題。
增刪時間複雜度分析:
- 最好的情況:插入或刪除尾部:O(1)
- 最壞情況:插入或刪除頭部:O(n)
- 平均情況:(n-1)/2
線性表順序儲存結構的優缺點
優點:
- 無須為表示表中元素之間的邏輯關係而增加額外的儲存空間。
- 可以快速的存取表中的任一位置的元素。
缺點:
- 插入和刪除操作需要搬移大量資料。
- 當線性表長度變化較大時,難以確定儲存空間的容量。
- 容易造成儲存空間的碎片。
線性表的鏈式儲存結構
鏈式儲存結構的定義
特點:
-
用一組任意的儲存單元儲存線性表的資料元素,這組儲存單元可以連續,也可以不連續。(順序儲存要求地址連續)
-
空間、邏輯負擔:順序結構中,每個資料元素只需要存資料元素的資訊,而鏈式結構中,還需要儲存它的後繼元素的儲存地址。
- 即是順序儲存結構的前驅後繼是地址相鄰。
- 而鏈式儲存結構的前驅後繼是靠指標指向。
頭指標與頭結點的異同
頭指標:
- 頭指標是指指向第一個結點的指標。若連結串列有頭結點(哨兵),則是指向頭結點的指標。
- 頭指標具有表示作用,所以通常當連結串列的控制程式碼。頭指標也冠以連結串列的名字。
- 無論連結串列是否為空,頭指標都不為空,因為它指向連結串列的實體。
- 頭指標是連結串列的必要元素。
頭結點:
- 頭結點是為了操作的統一和方便而設立的,就是不用考慮無結點時的連結串列操作。
- 頭結點放在第一個元素的結點之前,其資料域一般無意義。可以新增一些自定義的資料,用於管理連結串列。可以擴充套件成這個連結串列的控制塊。
- 頭結點不是連結串列的必要元素。
鏈式儲存結構的資料結構
雙向非通用迴圈連結串列相關節點:
- 根節點可以擴充套件成連結串列的控制塊。
- 在節點中掛載資訊。資訊結構可自定義。
/* mini 節點結構體 */
struct LIST_MINI_ITEM_LSS_T
{
/* 指向,(根據單、雙連結串列刪減以下內容) */
struct node *pxPrevious; // 上一個節點
struct node *pxNext; // 下一個節點
/* 節點屬性,(根據個人需求增減以下內容) */
uint32_t xItemValue; // 記號值,在雙向連結串列中為最大值
};
typedef struct LIST_MINI_ITEM_LSS_T ListMiniItem_t;
/* 連結串列結構體,即根節點 */
struct LIST_LSS_T
{
/* 節點屬性,(根據個人需求增減以下內容) \*/
uint32_t uxNumberOfItems; // 節點數,統計粘附在本連結串列上的節點數
struct LIST_ITEM_LSS_T * pxIndex; // 索引,連結串列索引,指向連結串列中的某個節點
struct LIST_MINI_ITEM_LSS_T xListEnd; // 連結串列根節點
};
typedef struct LIST_LSS_T List_t;
/*
* The linked list node
*/
struct LIST_ITEM_LSS_T
{
struct LIST_ITEM_LSS_T * pxNext; // 下一個
struct LIST_ITEM_LSS_T * pxPrevious; // 上一個
/* 節點屬性,(根據個人需求增減以下內容) */
uint32_t xItemValue; // 記號值,一般用於排序
void * pvOwner; // 掛鉤,即攜帶的資訊
void * pvContainer; // 歸屬,即屬於哪一個連結串列
};
typedef struct LIST_ITEM_LSS_T listItem_t;
雙向通用迴圈連結串列相關節點:
- 把節點放置待結構體中即可。
- 連結串列的每個元素型別一致。
/*
* Structure of a node in a doubly linked list.
*/
typedef struct LSS_LIST
{
struct LSS_LIST *pstPrev; /**< Current node's pointer to the previous node*/
struct LSS_LIST *pstNext; /**< Current node's pointer to the next node*/
} LSS_LIST;
typedef struct LSS_LIST listItem_t;
時間複雜度分析
- 單連結串列的插入和刪除:首先是遍歷查詢第 i 個元素,其次是插入和刪除操作。
- 所以時間複雜度是 O(n)。
- 增刪元素時,順序儲存結構需要挪動資料,O(n)。而鏈式儲存結構修改指向的指標 O(1)。
- 綜上,對於對元素增刪頻繁的,鏈式儲存結構比較好。
連結串列原始碼參考
順序儲存結構與鏈式儲存結構使用建議
- 若線性表多查詢,少插刪,宜採用順序儲存結構;若多查刪,宜採用單連結串列結構。
- 若線性表的元素個數變化大或不確定時,宜採用單連結串列;若確定或事先明確個數,用順序儲存效率更高。
靜態連結串列
問題:C 語言有指標,python 等物件導向的語言有引用。但是有些語言沒有指標也沒有引用,如何實現連結串列結構?
解決方案:結合陣列&連結串列。用陣列下標代替指標。即是遊標實現法。
靜態連結串列的定義
定義:用陣列描述的連結串列叫做靜態連結串列,或曰“遊標實現法”。
-
陣列的元素都是由兩個資料域組成的,data(資料)和 cur(遊標)。
-
陣列的每個下標都對應一個 data 和一個 cur。
- 資料域 data:用來存放資料元素,也就是我們要處理的資料。
- 遊標 cur:相當於單連結串列中的 next 指標,存放該元素的後繼在陣列中的下標。
-
備用連結串列:通常把未被使用的陣列元素稱為備用連結串列。(即陣列的後面還沒填充資料的空閒空間)
-
兩個特殊位置:
-
陣列第一個元素空間用於存放備用連結串列的第一個節點。
- space[0].cur 表示備用連結串列的第一個節點的下標。
- space[0].cur == (max_size - 1) 時表示備用連結串列已無空間。
-
陣列最後一個元素空間用於存放頭結點。
- space[max_size-1].cur 表示第一個有效資料元素的下標。
- space[max_size-1].cur == 0 時表示資料連結串列為空。
-
靜態連結串列的資料結構
/* 線性表的靜態連結串列儲存結構 */
typedef struct
{
elem_type data;
int cur; /* 遊標(Cursor) ,為0時表示無指向 */
} component_t;
component_t static_link_list[MAX_SIZE];
程式碼實現
靜態連結串列的實現
/** @file static_list.c
* @brief 簡要說明
* @details 詳細說明
* @author lzm
* @date 2021-09-05 22:12:22
* @version v1.0
* @copyright Copyright By lizhuming, All Rights Reserved
* @blog https://www.cnblogs.com/lizhuming/
*
**********************************************************
* @LOG 修改日誌:
**********************************************************
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 說明:連結串列操作的索引是 1 起的。 */
/* 線性表的靜態連結串列儲存結構 */
#define MAX_SIZE 100
typedef int elem_type;
typedef struct
{
elem_type data;
int cur; /* 遊標(Cursor) ,為0時表示無指向 */
}static_list_t;
static_list_t static_list[MAX_SIZE] = {0};
/**
* @name static_list_init
* @brief 將一維陣列space中各分量鏈成一個備用連結串列,space[0].cur為頭指標,"0"表示空指標
* @param
* @retval
* @author lzm
*/
int static_list_init(static_list_t *space)
{
int i = 0;
if(space == NULL)
{
return -1;
}
for (i=0; i<MAX_SIZE-1; i++)
space[i].cur = i+1;
space[MAX_SIZE-1].cur = 0; /* 目前靜態連結串列為空,最後一個元素的cur為0 */
return 0;
}
/**
* @name static_list_malloc
* @brief 從陣列備用連結串列中模擬 malloc 一個資料空間出來
* @param
* @retval 下標
* @author lzm
*/
int static_list_malloc(static_list_t *space)
{
int index = 0;
if(space == NULL)
return -1;
index = space[0].cur;
if(space[0].cur)
space[0].cur = space[index].cur;
return index;
}
/**
* @name static_list_free
* @brief 把一個資料空間放會備用連結串列中
* @param
* @retval
* @author lzm
*/
int static_list_free(static_list_t *space, int index)
{
if(space == NULL || index <= 0 || index >= MAX_SIZE)
return -1;
space[index].cur = space[0].cur;
space[0].cur = index;
return 0;
}
/**
* @name static_list_length
* @brief
* @param
* @retval
* @author lzm
*/
int static_list_length(static_list_t *space)
{
int cnt = 0;
int index = 0;
if(space == NULL)
return -1;
index = space[MAX_SIZE-1].cur;
while(index)
{
cnt++;
index = space[index].cur;
}
return cnt;
}
/**
* @name static_list_inster
* @brief 在space資料連結串列中第cur_inster個元素前插入新的元素。(程式設計技巧:找出前一個便好操作了)
* @param
* @retval
* @author lzm
*/
int static_list_inster(static_list_t *space, int cur_inster, elem_type elem)
{
int i = 0;
int index = 0;
int index_m = 0;
if(space == NULL || cur_inster < 1 || cur_inster > (static_list_length(space) + 1))
return -1;
index_m = static_list_malloc(space);
if(index_m <= 0)
return -1;
space[index_m].data = elem;
index = MAX_SIZE-1;
for(i = 1; i < cur_inster; i++)
index = space[index].cur;
space[index_m].cur = space[index].cur;
space[index].cur = index_m;
return 0;
}
/**
* @name static_list_delete
* @brief 在space資料連結串列中刪除第cur_delete個元素。
* @param
* @retval
* @author lzm
*/
int static_list_delete(static_list_t *space, int cur_delete)
{
int i = 0;
int index = 0;
int index_d = 0;
if(space == NULL)
return -1;
if(space == NULL || cur_delete < 1 || cur_delete > static_list_length(space))
return -1;
index = MAX_SIZE - 1;
for(i = 1; i < cur_delete; i++) // 獲取實際需要刪除的前一個
index = space[index].cur;
index_d = space[index].cur;
space[index].cur = space[index_d].cur;
static_list_free(space, index_d);
return 0;
}