(2)簡單說:鏈表就是用來存儲數據的。鏈表用來存數據相對於數組來說優點就是靈活性,需要多少個動態分配多少個,不佔用額外的內存。數組的優勢是使用簡單(簡單粗暴)。
// 構建一個鏈表的節點
struct node
{
int data; // 有效數據
struct node *pNext; // 指向下一個節點的指針
};
(1)鏈表的內存要求比較靈活,不能用棧,也不能用data數據段。只能用堆內存。
(2)使用堆內存來創建一個鏈表節點的步驟:1、申請堆內存,大小爲一個節點的大小(檢查申請結果是否正確);2、清理申請到的堆內存;3、把申請到的堆內存當作一個新節點;4、填充你哦個新節點的有效數據和指針區域。
// 定義頭指針
struct node *pHeader = NULL;
/********************************************************************/
// 每創建一個新的節點,把這個新的節點和它前一個節點關聯起來
// 創建一個鏈表節點
struct node *p = (struct node *)malloc(sizeof(struct node));
if (NULL == p)
{
printf("malloc error.\n");
return -1;
}
// 清理申請到的堆內存
bzero(p, sizeof(struct node)); // 也可以用memset:memset(p,0,sizeof(struct node));
// 填充節點
p->data = 1;
p->pNext = NULL; // 將來要指向下一個節點的首地址
// 實際操作時將下一個節點malloc返回的指針賦值給這個
pHeader = p; // 將本節點和它前面的頭指針關聯起來
/********************************************************************/
/********************************************************************/
// 每創建一個新的節點,把這個新的節點和它前一個節點關聯起來
// 創建一個鏈表節點
struct node *p1 = (struct node *)malloc(sizeof(struct node));
if (NULL == p1)
{
printf("malloc error.\n");
return -1;
}
// 清理申請到的堆內存
bzero(p1, sizeof(struct node));
// 填充節點
p1->data = 2;
p1->pNext = NULL; // 將來要指向下一個節點的首地址
// 實際操作時將下一個節點malloc返回的指針賦值給這個
p->pNext = p1; // 將本節點和它前面的頭指針關聯起來
/********************************************************************/
/********************************************************************/
// 每創建一個新的節點,把這個新的節點和它前一個節點關聯起來
// 創建一個鏈表節點
struct node *p2 = (struct node *)malloc(sizeof(struct node));
if (NULL == p2)
{
printf("malloc error.\n");
return -1;
}
// 清理申請到的堆內存
bzero(p2, sizeof(struct node));
// 填充節點
p2->data = 3;
p1->pNext = p2; // 將來要指向下一個節點的首地址
// 實際操作時將下一個節點malloc返回的指針賦值給這個
// 訪問鏈表第1個節點的有效數據
printf("node1 data: %d.\n", pHeader->data);
printf("p->data: %d.\n", p->data); // pHeader->data等同於p->data
// 訪問鏈表第2個節點的有效數據
printf("node2 data: %d.\n", pHeader->pNext->data);
printf("p1->data: %d.\n", p1->data);
// pHeader->pNext->data等同於p1->data
// 訪問鏈表第3個節點的有效數據
printf("node3 data: %d.\n", pHeader->pNext->pNext->data);
printf("p2->data: %d.\n", p2->data);
// pHeader->pNext->pNext->data等同於p2->data
將創建節點的代碼封裝成一個函數
(1)封裝時的關鍵點就是函數的接口(函數參數和返回值)的設計struct node * create_node(int data)
{
struct node *p = (struct node *)malloc(sizeof(struct node));
if (NULL == p)
{
printf("malloc error.\n");
return NULL;
}
// 清理申請到的堆內存
bzero(p, sizeof(struct node));
// 填充節點
p->data = data;
p->pNext = NULL;
return p;
}
pHeader = create_node(1);
// 將本節點和它前面的頭指針關聯起來
pHeader->pNext = create_node(432);
// 將本節點和它前面的頭指針關聯起來
pHeader->pNext->pNext = create_node(123);
// 將來要指向下一個節點的首地址
從鏈表尾部插入新節點
// 思路:由頭指針向後遍歷,直到走到原來的最後一個節點。原來最後一個節點裏面的pNext是NULL,現在我們只要將它改成new就可以了。添加了之後新節點就變成了最後一個。
void insert_tail(struct node *pH, struct node *new)
{
// 分兩步來完成插入
// 第一步,先找到鏈表中最後一個節點
struct node *p = pH;
while (NULL != p->pNext)
{
p = p->pNext; // 往後走一個節點
}
// 第二步,將新節點插入到最後一個節點尾部
p->pNext = new;
}
// 計算添加了新的節點後總共有多少個節點,然後把這個數寫進頭節點中。
//由上一個函數修改所得
void insert_tail(struct node *pH, struct node *new)
{
int cnt = 0;
// 分兩步來完成插入
// 第一步,先找到鏈表中最後一個節點
struct node *p = pH;
while (NULL != p->pNext)
{
p = p->pNext; // 往後走一個節點
cnt++;
}
// 第二步,將新節點插入到最後一個節點尾部
p->pNext = new;
pH->data = cnt + 1;
}
int main(void)
{
// 定義頭指針
//struct node *pHeader = NULL; // 這樣直接insert_tail會段錯誤。
struct node *pHeader = create_node(0);
insert_tail(pHeader, create_node(1));
insert_tail(pHeader, create_node(2));
insert_tail(pHeader, create_node(3));
return 0;
}
4.9.5.從鏈表頭部插入新節點
// 思路:
void insert_head(struct node *pH, struct node *new)
{
// 第1步: 新節點的next指向原來的第一個節點
new->pNext = pH->pNext;
// 第2步: 頭節點的next指向新節點的地址
pH->pNext = new;
// 第3步: 頭節點中的計數要加1
pH->data += 1;
}
int main(void)
{
// 定義頭指針
//struct node *pHeader = NULL; // 這樣直接insert_tail會段錯誤。
struct node *pHeader = create_node(0);
insert_head(pHeader, create_node(1));
insert_tail(pHeader, create_node(2));
insert_head(pHeader, create_node(3));
printf("beader node data: %d.\n", pHeader->data);
// 訪問鏈表第1個節點的有效數據
printf("node1 data: %d.\n", pHeader->pNext->data);
// 訪問鏈表第2個節點的有效數據
printf("node2 data: %d.\n", pHeader->pNext->pNext->data);
// 訪問鏈表第3個節點的有效數據
printf("node3 data: %d.\n", pHeader->pNext->pNext->pNext->data);
return 0;
}
4.9.6.單鏈表的算法之遍歷節點
// 遍歷單鏈表,pH爲指向單鏈表的頭指針,遍歷的節點數據打印出來
void bianli(struct node*pH)
{
//pH->data // 頭節點數據,不是鏈表的常規數據,不要算進去了
//struct node *p = pH; // 錯誤,因爲頭指針後面是頭節點
struct node *p = pH->pNext; // p直接走到第一個節點
printf("-----------開始遍歷-----------\n");
while (NULL != p->pNext) // 是不是最後一個節點
{
printf("node data: %d.\n", p->data);
p = p->pNext; // 走到下一個節點,也就是循環增量
}
printf("node data: %d.\n", p->data);
printf("-------------完了-------------\n");
}
// 1、思考下爲什麼這樣能解決問題;2、思考下設計鏈表時爲什麼要設計頭節點
void bianli2(struct node*pH)
{
//pH->data // 頭節點數據,不是鏈表的常規數據,不要算進去了
struct node *p = pH; // 頭指針後面是頭節點
printf("-----------開始遍歷-----------\n");
while (NULL != p->pNext) // 是不是最後一個節點
{
p = p->pNext; // 走到下一個節點,也就是循環增量
printf("node data: %d.\n", p->data);
}
printf("-------------完了-------------\n");
}
單鏈表的算法之刪除節點
// 從鏈表pH中刪除節點,待刪除的節點的特徵是數據區等於data
// 返回值:當找到並且成功刪除了節點則返回0,當未找到節點時返回-1
int delete_node(struct node*pH, int data)
{
// 找到這個待刪除的節點,通過遍歷鏈表來查找
struct node *p = pH; // 用來指向當前節點
struct node *pPrev = NULL; // 用來指向當前節點的前一個節點
while (NULL != p->pNext) // 是不是最後一個節點
{
pPrev = p; // 在p走向下一個節點前先將其保存
p = p->pNext; // 走到下一個節點,也就是循環增量
// 判斷這個節點是不是我們要找的那個節點
if (p->data == data)
{
// 找到了節點,處理這個節點
// 分爲2種情況,一個是找到的是普通節點,另一個是找到的是尾節點
// 刪除節點的困難點在於:通過鏈表的遍歷依次訪問各個節點,找到這個節點
// 後p指向了這個節點,但是要刪除這個節點關鍵要操作前一個節點,但是這
// 時候已經沒有指針指向前一個節點了,所以沒法操作。解決方案就是增加
// 一個指針指向當前節點的前一個節點
if (NULL == p->pNext)
{
// 尾節點
pPrev->pNext = NULL; // 原來尾節點的前一個節點變成新尾節點
free(p); // 釋放原來的尾節點的內存
}
else
{
// 普通節點
pPrev->pNext = p->pNext; // 要刪除的節點的前一個節點和它的後一個節點相連,這樣就把要刪除的節點給摘出來了
free(p);
}
// 處理完成之後退出程序
return 0;
}
}
// 到這裏還沒找到,說明鏈表中沒有我們想要的節點
printf("沒找到這個節點.\n");
return -1;
}
delete_node(pHeader, 12);
4.9.8.2、單鏈表逆序算法分析
//思路:首先遍歷原鏈表,然後將原鏈表中的頭指針和頭節點作爲新鏈表的頭指針和頭節點,原鏈表中的有效節點挨個依次取出來,採用頭插入的方法插入新鏈表中即可。
// 將pH指向的鏈表逆序
void reverse_linkedlist(struct node *pH)
{
struct node *p = pH->pNext; // pH指向頭節點,p指向第1個有效節點
struct node *pBack; // 保存當前節點的後一個節點地址
// 當鏈表沒有有效節點或者只有一個有效節點時,逆序不用做任何操作
if ((NULL ==p) || (NULL == p->pNext))
return;
// 當鏈表有2個及2個以上節點時才需要真正進行逆序操作
while (NULL != p->pNext) // 是不是最後一個節點
{
// 原鏈表中第一個有效節點將是逆序後新鏈表的尾節點,尾節點的pNext指向NULL
pBack = p->pNext; // 保存p節點後面一個節點地址
if (p == pH->pNext)
{
// 原鏈表第一個有效節點
p->pNext = NULL;
}
else
{
// 原鏈表的非第1個有效節點
p->pNext = pH->pNext;
}
pH->pNext = p;
//p = p->pNext; // 這樣已經不行了,因爲p->pNext已經被改過了
p = pBack; // 走到下一個節點
}
// 循環結束後,最後一個節點仍然缺失
insert_head(pH, p);
}