DS---順序表和連結串列
一、線性表
線性表(linear list)是n個具有相同特性的資料元素的有限序列,是一種在實際中廣泛使用的資料結構,常見的線性表:順序表、連結串列、棧、佇列、字串…
線性表在邏輯上是線性結構,也就說是連續的一條直線。但是在物理結構上並不一定是連續的,線性表在物理上儲存時,通常以陣列和鏈式結構的形式儲存。
二、順序表
1.基本概念
順序表是用一段實體地址連續的儲存單元依次儲存資料元素的線性結構,一般情況下采用陣列儲存。在陣列上完成資料的增刪查改。
順序表一般可以分為如下兩類:
1.靜態順序表:表的大小不可改變的順序表。
2.動態順序表:可以根據需求對順序表進行擴容的順序表。
靜態順序表結構定義
struct SeqList
{
SLDataType arry[MAX_SIZE];//固定長度的陣列
int size;//順序表中有效元素的個數
}動態順序表結構定義
struct SeqList
{
SLDataType* arr;//可變長度的陣列
int size;//有效元素的個數
int capacity;//表的容量
}
2.順序表的實現
// 順序表初始化
void SeqListInit(SeqList* psl, int capacity)
{
//申請空間
psl->array = (SLDataType*)malloc(sizeof(SeqList) *capacity);
//判斷申請空間是否成功
if (psl == NULL)
{
printf("申請空間失敗!\n");
exit(-1);
}
psl->size = 0;
psl->capicity = capacity;
}
// 順序表銷燬
void SeqListDestory(SeqList* psl)
{
assert(psl);
//釋放節點,置空指標
free(psl->array);
psl->array = NULL;
psl->capicity = psl->size = 0;
}
// 順序表列印
void SeqListPrint(SeqList* psl)
{
assert(psl);
int i = 0;
for (i = 0; i < psl->size; i++)
printf("%d ",psl->array[i]);
printf("\n");
}
// 檢查空間,如果滿了,進行增容
void CheckCapacity(SeqList* psl)
{
assert(psl);
//順序表已經滿了,進行擴容,擴容後的順序表的大小時原來的兩倍
if (psl->size == psl->capicity)
{
psl->array = (SLDataType*)realloc(psl->array, sizeof(SeqList)*(psl->capicity) * 2);
psl->capicity *= 2;
}
}
// 順序表尾插
void SeqListPushBack(SeqList* psl, SLDataType x)
{
assert(psl);
//檢查順序表是否還有容量,如果沒有就進行擴容
CheckCapacity(psl);
//插入資料
psl->array[psl->size] = x;
//size+1
psl->size++;
}
// 順序表尾刪
void SeqListPopBack(SeqList* psl)
{
assert(psl);
//判斷順序表是否為空
if (psl->size == 0)
return;
//刪除最後一個數據,即將有效資料個數-1
psl->size--;
}
// 順序表頭插
void SeqListPushFront(SeqList* psl, SLDataType x)
{
assert(psl);
//判斷表的容量,如果不夠則進行擴容
CheckCapacity(psl);
//如果表中的有效資料個數為0,則直接插入
if (psl->size == 0)
psl->array[psl->size] = x;
else
{
//將有效元素後移一位,在將x插入
int i = psl->size;
for (; i > 0;i--)
{
psl->array[i] = psl->array[i - 1];
}
psl->array[0] = x;
}
//有效資料個數+1
psl->size++;
}
// 順序表頭刪
void SeqListPopFront(SeqList* psl)
{
assert(psl);
//判斷有效資料個數,如果為0不進行任何操作
if (psl->size == 0)
{
return;
}
//只有一個有效資料,則直接刪除
else if (psl->size == 1)
{
psl->size = 0;
}
else
{
int i = 0;
//將後邊元素前移一個位置
for (; i < psl->size-1; i++)
{
psl->array[i] = psl->array[i + 1];
}
psl->size--;
}
}
// 順序表查詢
int SeqListFind(SeqList* psl, SLDataType x)
{
assert(psl);
//判斷表中有效元素是否為空
assert(psl->size);
int i = 0;
//遍歷順序表,如果找到了返回對應的下標,如果沒有找到返回-1
for (; i < psl->size; i++)
{
if (psl->array[i] == x)
return i;
}
return -1;
}
// 順序表在pos位置插入x
void SeqListInsert(SeqList* psl, int pos, SLDataType x)
{
assert(psl);
//判斷順序表容量
CheckCapacity(psl);
//判斷插入位置是否合法
if (pos < 0 || pos > psl->size)
{
printf("插入位置不合法\n");
return;
}
int i = psl->size;
//移動從pos開始的資料,後移一個位置
for (; i > pos; i--)
{
psl->array[i] = psl->array[i - 1];
}
psl->array[pos] = x;
psl->size++;
}
// 順序表刪除pos位置的值
void SeqListErase(SeqList* psl, int pos)
{
assert(psl);
//判斷刪除位置是否合法
if (pos < 0 || pos > psl->size)
{
printf("刪除位置不合法\n");
return;
}
else
{
int i = pos + 1;
for (; i < psl->size; i++)
{
psl->array[i - 1] = psl->array[i];
}
psl->size--;
}
}
三、連結串列
1.基本概念
連結串列是一種物理儲存結構上非連續、非順序的儲存結構,資料元素的邏輯順序是通過連結串列中的指標連結次序實現的 。
實際中連結串列的結構非常多樣,以下情況組合起來就有8種連結串列結構:
- 單向、雙向
- 帶頭、不帶頭
- 迴圈、非迴圈
連結串列分類但從以上幾個方面就可以分為8中,但最常用的只有不帶頭結點的單鏈表和帶頭結點的雙向迴圈連結串列兩種。
2.不帶頭結點的單鏈表
單鏈表結構定義
struct SListNode
{
SLTDateType data;//節點與域
struct SListNode* next;//指標域
};
介面的實現
// 動態申請一個節點
SListNode* BuySListNode(SLTDateType x)
{
SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
if (newNode == NULL)
{
printf("申請記憶體失敗!\n");
exit(-1);
}
else
{
newNode->data = x;
newNode->next = NULL;
}
return newNode;
}
// 單鏈表列印
void SListPrint(SListNode* plist)
{
assert(plist);
SListNode* curr = plist;
while (curr)
{
printf("%d ",curr->data);
curr = curr->next;
}
printf("\n");
}
// 單鏈表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
//建立節點
SListNode* buyNode = BuySListNode(x);
//如果連結串列為空,則該節點就為第一個節點
if (*pplist == NULL)
{
*pplist = buyNode;
}
else
{
//找到尾結點進行插入
SListNode* tail = *pplist;
while (tail->next)
{
tail = tail->next;
}
tail->next = buyNode;
}
}
// 單鏈表的頭插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
//建立節點
SListNode* buyNode = BuySListNode(x);
buyNode->next = *pplist;
*pplist = buyNode;
}
// 單鏈表的尾刪
void SListPopBack(SListNode** pplist)
{
assert(*pplist);
//找到尾結點
SListNode* preTail = NULL;
SListNode* tail = *pplist;
while (tail->next)
{
preTail = tail;
tail = tail->next;
}
preTail->next = NULL;
free(tail);
tail = NULL;
}
// 單鏈表頭刪
void SListPopFront(SListNode** pplist)
{
assert(*pplist);
SListNode* head = *pplist;
*pplist = (*pplist)->next;
free(head);
head = NULL;
}
// 單鏈表查詢
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
assert(plist);
SListNode* curr = plist;
while (curr)
{
if (curr->data == x)
return curr;
curr = curr->next;
}
return NULL;
}
// 單鏈表在pos位置之後插入x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
assert(pos);
//建立節點
SListNode* buyNode = BuySListNode(x);
SListNode* posNext = pos->next;
pos->next = buyNode;
buyNode->next = posNext;
}
// 單鏈表刪除pos位置之後的值
void SListEraseAfter(SListNode* pos)
{
assert(pos);
assert(pos->next);
pos->next = pos->next->next;
}
3.帶頭結點的雙向迴圈連結串列
雙迴圈連結串列的結構定義
struct ListNode
{
LTDataType _data;
struct ListNode* _next;
struct ListNode* _prev;
};
介面實現
//建立一個節點
ListNode* BuyNode(LTDataType x)
{
ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
if (newNode == NULL)
{
printf("申請節點失敗\n");
exit(-1);
}
newNode->_data = x;
newNode->_next = NULL;
newNode->_prev = NULL;
return newNode;
}
// 建立返回連結串列的頭結點.
ListNode* ListCreate()
{
ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
if (newNode == NULL)
{
printf("申請節點失敗\n");
exit(-1);
}
newNode->_next = newNode;
newNode->_prev = newNode;
return newNode;
}
// 雙向連結串列銷燬
void ListDestory(ListNode* plist)
{
assert(plist);
ListNode* curr= plist;
while (curr != plist)
{
ListNode* del = curr;
curr = curr->_next;
free(del);
}
plist = NULL;
}
// 雙向連結串列列印
void ListPrint(ListNode* plist)
{
assert(plist);
ListNode* curr = plist->_next;
while (curr != plist)
{
printf("%d ", curr->_data);
curr = curr->_next;
}
printf("\n");
}
// 雙向連結串列尾插
void ListPushBack(ListNode* plist, LTDataType x)
{
//建立一個節點
ListNode* buyNode = BuyNode(x);
buyNode->_next = plist;
buyNode->_prev = plist->_prev;
plist->_prev->_next = buyNode;
plist->_prev = buyNode;
}
// 雙向連結串列尾刪
void ListPopBack(ListNode* plist)
{
ListNode* tail = plist->_prev;
plist->_prev->_prev->_next = plist;
plist->_prev = plist->_prev->_prev;
free(tail);
tail = NULL;
}
// 雙向連結串列頭插
void ListPushFront(ListNode* plist, LTDataType x)
{
//建立一個節點
ListNode* buyNode = BuyNode(x);
buyNode->_next = plist->_next;
plist->_next->_prev = buyNode;
plist->_next = buyNode;
buyNode->_prev = plist;
}
// 雙向連結串列頭刪
void ListPopFront(ListNode* plist)
{
ListNode* head = plist->_next;
plist->_next->_next->_prev = plist;
plist->_next = plist->_next->_next;
free(head);
head = NULL;
}
// 雙向連結串列查詢
ListNode* ListFind(ListNode* plist, LTDataType x)
{
ListNode* curr = plist->_next;
while (curr != plist)
{
if (curr->_data == x)
return curr;
curr = curr->_next;
}
return NULL;
}
// 雙向連結串列在pos的前面進行插入
void ListInsert(ListNode* pos, LTDataType x)
{
//建立一個節點
ListNode* buyNode = BuyNode(x);
buyNode->_next = pos;
buyNode->_prev = pos->_prev;
pos->_prev->_next = buyNode;
pos->_prev = buyNode;
}
// 雙向連結串列刪除pos位置的節點
void ListErase(ListNode* pos)
{
ListNode* erase = pos;
pos->_prev->_next = pos->_next;
pos->_next->_prev = pos->_prev;
free(erase);
erase = NULL;
}
順序表和連結串列的比較
順序表
優點:空間連續、支援隨機訪問
缺點:中間或前面部分的插入刪除時間複雜度O(N) ;增容的代價比較大。連結串列
缺點:以節點為單位儲存,不支援隨機訪問
優點:任意位置插入刪除時間複雜度為O(1) ;沒有增容問題,插入一個開闢一個空間。