前面介紹了順序表的特點和實現!但是順序表有很多的不足,比如要給順序表的前面插入一個數據,就必須先把後面的數據先一個一個的往後挪動,然後再將所要插入的數據放進去。就相當於一個數組一樣。
還有就是順序表的大小分配,如果採用靜態分配內存的方式,那麼勢必就會造成剩餘內存的浪費,不利於CPU工作。要是用動態內存分配,可以減少內存浪費情況,但是一次性開闢的內存不能太大也不能太小,遇到一個結構體數據剛剛存放滿,這時候在想增加數據就要再申請空間,那麼申請的空間只用了一個,其他的也就造成了浪費。
所以這時候爲了方便存儲,和更加節省空間,就採用了單鏈表的方式。先來看看爲什麼單鏈表有這麼多好處
單鏈表採用上一個節點的頭結點和下個節點相鄰,相當於把好多的數據直接連起來,用的時候只需要改變節點之間的連接。而且增容是一次增加一個節點的大小,實現了真正的用多少分配多少。
寫單鏈表的時候一定要注意你的首節點,別丟了,丟了就找不回來。還有就是在刪除節點的時候一定要對那個節點負責,不然讓它成爲野指針了,對誰都不好。
單鏈表如何實現,還有接口怎麼封裝,具體在程序中看一下,重點部分都做了標識。
ListNode.h
#ifndef _LISTNODE_H_
#define _LISTNODE_H_
#include <stdio.h>
#include <windows.h>
#include <assert.h>
typedef int Datatype;
typedef struct ListNode
{
Datatype data;
struct ListNode *next;
}ListNode;
void PushBack(ListNode **pplist, Datatype x);//從後面插入一個data爲x的節點
void PopBack(ListNode **plist); //從後面刪除一個節點
void PrintList(ListNode *plist); //打印單鏈表
void PushFront(ListNode **pplist, Datatype x); //從前面插入一個data爲x的節點
void PopFront(ListNode **pplist); //從前面刪除一個節點
ListNode* Find(ListNode* pList, Datatype x); //查找data爲x的節點,並返回這個節點
//在pos前插入||刪除
void Insert(ListNode **pplist, ListNode *pos, Datatype x);//在所指定的節點前插入一個data爲x的節點
void Erase(ListNode **pplist, ListNode *pos);//刪除指定的節點
#endif
#include "ListNode.h"
ListNode *BuyNode(Datatype x) //生成節點封裝接口
{
ListNode *Node = (ListNode *)malloc(sizeof(ListNode));// 以動態的形式生成一個大小爲一個結構體大小的節點
Node->data = x; //所要生成的節點數據
Node->next = NULL; //將生成節點的下個賦爲空
return Node;
}
void PrintList(ListNode *plist)//打印單鏈表
{
ListNode *cur = plist;
while(cur != NULL)
{
printf("%d->",cur->data);//將單鏈表的數據正向輸出
cur = cur->next;
}
printf("NULL\n");
}
void PushBack(ListNode **pplist, Datatype x)//在做這些之前要先理清思路,都有哪幾種情況,然後敲
{
//1.空 2.只有一個 3.多個
assert(pplist);
if(*pplist == NULL)//沒有節點的時候直接把節點給首節點
{
*pplist = BuyNode(x);
}
else if((*pplist)->next == NULL)
{
(*pplist)->next = BuyNode(x);//只有一個就把生成的節點串在首節點後面
}
else
{
ListNode *cur = *pplist;
while((cur)->next != NULL)
{
cur = cur->next;
}
cur->next = BuyNode(x);
}
}
void PopBack(ListNode **pplist)//和Push一樣都要考慮清楚,然後就很好寫
{
//1.NULL 2.1個 3.多個
assert(pplist);
if(*pplist == NULL)
{
return;
}
else if((*pplist)->next == NULL)
{
free(*pplist);//只有一個的話直接釋放
*pplist = NULL;//一定要將這個節點置空,不然容易成爲野指針
}
else
{
ListNode *tmp = *pplist;//先定義兩個指針,分別保存要刪除的節點和上一個節點
ListNode *cur = tmp;
while(tmp->next != NULL)
{
cur = tmp;
tmp = tmp->next;
}
free(tmp);//刪除指定節點
cur->next = NULL;//將上個節點的next給置空
tmp = NULL;//記得置空
}
}
void PushFront(ListNode **pplist, Datatype x)//和尾插一樣,只是情況還要具體分析
{
//1.NULL 2.一個 3.多個
assert(pplist);
if(*pplist == NULL)
{
*pplist = BuyNode(x);
}
else
{
ListNode *cur = BuyNode(x);
cur->next = *pplist;
*pplist = cur;
}
}
void PopFront(ListNode **pplist)
{
//1.NULL 2.一個 3.多個
assert(pplist);
if(*pplist == NULL)
{
return;
}
else if((*pplist)->next == NULL)
{
free(*pplist);
*pplist = NULL;
}
else
{
ListNode *tmp = *pplist;
*pplist = tmp->next;
free(tmp);
}
}
ListNode* Find(ListNode* plist, Datatype x)//查找節點並不改變數據,所以不用傳結構體指針的地址
{
while(plist)
{
if(plist->data == x)//當找到要查找的數時,就直接返回
{
return plist;
}
plist = plist->next;
}
//perror("Find"); //出錯打印錯誤信息
//system("pause");
//exit(EXIT_FAILURE);//程序直接退出
return NULL;
}
void Insert(ListNode **pplist, ListNode *pos, Datatype x)//先調Find
{
//1.NULL 2.在第一個前插或只有一個數據 3.多個
assert(pplist);
assert(pos);
if(*pplist == NULL)
{
*pplist = BuyNode(x);
}
else if(((*pplist)->next == NULL) || (pos == *pplist))
{
PushFront(pplist, x);
}
else
{
ListNode *tmp = *pplist;//這裏要找到指定插入節點的上個節點和下個節點
while(tmp->next != pos)
{
tmp = tmp->next;
}
ListNode *p = BuyNode(x);
tmp->next = p;
p->next = pos;
}
}
void Erase(ListNode **pplist, ListNode *pos)//要刪除首先就要調用Find函數查找到相應的節點
{
//1.NULL或1個 2.多個
assert(pplist);
assert(pos);
if(((*pplist) == NULL) || ((*pplist)->next) == NULL)//只有一個或者沒有節點時直接採用前刪
{
PopFront(pplist);
}
else
{
ListNode *tmp = *pplist;
while(tmp->next != pos)
{
tmp = tmp->next;
}
tmp->next = pos->next;
free(pos);
pos = NULL;
}
}
test.cpp
#include "ListNode.h"
struct ListNode* plist;
void test1()
{
PushBack(&plist, 1);
PushBack(&plist, 2);
PushBack(&plist, 3);
PushBack(&plist, 4);
PopBack(&plist);
PopBack(&plist);
PopBack(&plist);
PopBack(&plist);
PopBack(&plist);
PrintList(plist);
}
void test2()
{
PushFront(&plist, 1);
PushFront(&plist, 2);
PushFront(&plist, 3);
PushFront(&plist, 4);
PrintList(plist);
PopFront(&plist);
PopFront(&plist);
PopFront(&plist);
PopFront(&plist);
PopFront(&plist);
PrintList(plist);
}
void test3()
{
ListNode *pos = NULL;
PushBack(&plist, 1);
/*PushBack(&plist, 2);
PushBack(&plist, 3);
PushBack(&plist, 4);*/
pos = Find(plist, 2);
Insert(&plist, pos, 5);
//Erase(&plist, pos);
PrintList(plist);
}
int main()
{
//test1();
//test2();
test3();
system("pause");
return 0;
}
爲了更加明確的瞭解鏈表的增刪查改,在Linux環境下來演示一下具體的過程
#include "ListNode.h"
struct ListNode* plist;
void test1()
{
int i = 0;
int j = 0;
printf("尾插,尾刪\n");
printf("\n");
while(i<5){
PushBack(&plist, i++);
PrintList(plist);
sleep(1);
}
while(j<5){
PopBack(&plist);
PrintList(plist);
sleep(1);
j++;
}
}
void test2()
{
int i = 0;
int j = 0;
printf("前插,前刪\n");
printf("\n");
while(i<5){
PushFront(&plist, i);
PrintList(plist);
sleep(1);
i++;
}while(j<5){
PopFront(&plist);
PrintList(plist);
sleep(1);
j++;
}
}
void test3()
{
int i = 0;
ListNode *pos = NULL;
printf("鏈表的節點插入、刪除和查找\n");
printf("\n");
while(i<5){
PushBack(&plist, i++);
PrintList(plist);
sleep(1);
}
pos = Find(plist, 2);
printf("查找單鏈表裏數據爲2的節點i\n");
printf("給2的前面插入5\n");
Insert(&plist, pos, 5);
PrintList(plist);
sleep(1);
printf("刪除2\n");
Erase(&plist, pos);
PrintList(plist);
}
int main()
{
test1();
test2();
test3();
return 0;
}