一.鏈表的概念及結(jié)構(gòu)
概念: 鏈表是?種 物理存儲結(jié)構(gòu)上非連續(xù)、非順序的存儲結(jié)構(gòu),數(shù)據(jù)元素的邏輯順序是通過鏈表中的指針鏈接次序?qū)崿F(xiàn)的 。
鏈表的結(jié)構(gòu)跟???廂相似,淡季時?次的?廂會相應(yīng)減少,旺季時?次的?廂會額外增加?節(jié)。只需要將???的某節(jié)?廂去掉/加上,不會影響其他?廂,每節(jié)?廂都是獨?存在的。
?廂是獨?存在的,且每節(jié)?廂都有??。想象?下這樣的場景,假設(shè)每節(jié)?廂的??都是鎖上的狀態(tài),需要不同的鑰匙才能解鎖,每次只能攜帶?把鑰匙的情況下如何從?頭?到?尾?
最簡單的做法:每節(jié)?廂?都放?把下?節(jié)?廂的鑰匙。
在鏈表?,每節(jié)“?廂”是什么樣的呢?
與順序表不同的是,鏈表?的每節(jié)"?廂"都是獨?申請下來的空間,我們稱之為“結(jié)點/節(jié)點”
節(jié)點的組成主要有兩個部分:當(dāng)前節(jié)點要保存的數(shù)據(jù)和保存下?個節(jié)點的地址(指針變量)。
圖中指針變量 plist保存的是第?個節(jié)點的地址,我們稱plist此時“指向”第?個節(jié)點,如果我們希望plist“指向”第?個節(jié)點時,只需要修改plist保存的內(nèi)容為0x0012FFA0。
為什么還需要指針變量來保存下?個節(jié)點的位置?
鏈表中每個節(jié)點都是獨?申請的(即需要插?數(shù)據(jù)時才去申請?塊節(jié)點的空間),我們需要通過指針變量來保存下?個節(jié)點位置才能從當(dāng)前節(jié)點找到下?個節(jié)點。
結(jié)合前?學(xué)到的結(jié)構(gòu)體知識,我們可以給出每個節(jié)點對應(yīng)的結(jié)構(gòu)體代碼:
假設(shè)當(dāng)前保存的節(jié)點為整型:
struct SListNode
{
int data; //節(jié)點數(shù)據(jù)
struct SListNode* next; //指針變量?保存下?個節(jié)點的地址
};
當(dāng)我們想要保存?個整型數(shù)據(jù)時,實際是向操作系統(tǒng)申請了?塊內(nèi)存,這個內(nèi)存不僅要保存整型數(shù)據(jù),也需要保存下?個節(jié)點的地址(當(dāng)下?個節(jié)點為空時保存的地址為空)。
當(dāng)我們想要從第?個節(jié)點?到最后?個節(jié)點時,只需要在前?個節(jié)點拿上下?個節(jié)點的地址(下?個節(jié)點的鑰匙)就可以了。
給定的鏈表結(jié)構(gòu)中,如何實現(xiàn)節(jié)點從頭到尾的打?。?/p>
思考:當(dāng)我們想保存的數(shù)據(jù)類型為字符型、浮點型或者其他?定義的類型時,該如何修改?
補充說明:
1、鏈?zhǔn)綑C構(gòu)在邏輯上是連續(xù)的,在物理結(jié)構(gòu)上不?定連續(xù)
2、節(jié)點?般是從堆上申請的
3、從堆上申請來的空間,是按照?定策略分配出來的,每次申請的空間可能連續(xù),可能不連續(xù)
二.單鏈表的實現(xiàn)
2.1單鏈表頭文件——功能函數(shù)的定義
SList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//創(chuàng)建鏈表節(jié)點結(jié)構(gòu)
typedef int SLDatatype;
typedef struct SListNode
{
SLDatatype data;//要保存的數(shù)據(jù)
struct SListNode* next;
}SLNode;
//創(chuàng)建幾個節(jié)點組成鏈表,并且打印鏈表
//打印
void SLNPrint(SLNode* phead);
//尾插
void SLNPushBack(SLNode** pphead, SLDatatype x);
//頭插
void SLNPushFront(SLNode** pphead, SLDatatype x);
//尾刪
void SLNPopBack(SLNode** pphead);
//頭刪
void SLNPopFront(SLNode** pphead);
//在指定位置插入刪除
//
//查找指定的pos
SLNode* SLNFind(SLNode** pphead, SLDatatype x);
//在指定位置之前插入
void SLNInsrt(SLNode** pphead, SLNode* pos, SLDatatype x);
//在指定位置之后插入刪除
void SLNInsrtAfter(SLNode* pos, SLDatatype x);
//刪除指定位置的數(shù)據(jù)
void SLNErase(SLNode** pphead, SLNode* pos);
//刪除指定位置之后的數(shù)據(jù)
void SLNEraseAfter(SLNode* pos);
//銷毀鏈表
void SLNDestroy(SLNode** pphead);
2.2單鏈表源文件——功能函數(shù)的實現(xiàn)
SList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
//打印
void SLNPrint(SLNode* phead)
{
//循環(huán)打印
//可以用phead直接來訪問,但是注意,當(dāng)代碼走到
//第16行的時候,此時phead已經(jīng)變成NULL了
//若代碼沒寫完,我還要繼續(xù)使用指向第一個節(jié)點的地址時,這時我就
//找不到第一個節(jié)點的地址
//所以為了避免第一個節(jié)點被更改,要定義一個新的來代替
SLNode* pcur = phead;
while (pcur)
{
printf("%d ->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
//創(chuàng)建新的節(jié)點
SLNode* SLByNode(SLDatatype x)
{
//開辟新的空間
SLNode* node = (SLNode*)malloc(sizeof(SLNode));
node->data = x;
node->next = NULL;
return node;
}
//尾插
void SLNPushBack(SLNode** pphead, SLDatatype x)
{
SLNode* node = SLByNode(x);
//判斷鏈表是否為空,如果為空,直接插入
if (*pphead == NULL)
{
*pphead = node;
return;
}
//說明鏈表不為空 ,找尾
SLNode* pcur = *pphead;
while (pcur->next != NULL)
{
pcur = pcur->next;
}
pcur->next = node;
}
//頭插
void SLNPushFront(SLNode** pphead, SLDatatype x)
{
//創(chuàng)建新的節(jié)點
SLNode* node = SLByNode(x);
//讓新的節(jié)點跟頭節(jié)點連起來
node->next = *pphead;
//再讓新節(jié)點成為頭節(jié)點
*pphead = node;
}
//尾刪
void SLNPopBack(SLNode** pphead)
{
//先判斷鏈表是否為空,如果為空就無法進行刪除操作
assert(pphead);
//還要判斷第一個節(jié)點不能為空
assert(*pphead);
//只有一個節(jié)點的情況
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
return;
}
//有多個節(jié)點的情況
//先找到尾節(jié)點/尾節(jié)點的前一個節(jié)點
SLNode* ptail = *pphead;
SLNode* prev = NULL;
while (ptail->next)
{
prev = ptail;
ptail = prev->next;
}
//讓尾節(jié)點的前一個節(jié)點指向不再指向尾節(jié)點,而是指向尾節(jié)點的下一個節(jié)點
prev->next = ptail->next;
//然后在刪除尾節(jié)點
free(ptail);
//為什么要將ptaii置為空指針?代碼后面明明沒有再使用ptail,因為打印鏈表的代碼有判斷節(jié)點的地址是否為空
ptail = NULL;
}
void SLNPopFront(SLNode** pphead)
{
//判斷鏈表不能為空
assert(pphead);
//判斷頭節(jié)點不能為空
assert(*pphead);
SLNode* del = *pphead;
*pphead = (*pphead)->next;
free(del);
//出于代碼規(guī)范
del = NULL;
}
//查找指定的pos
SLNode* SLNFind(SLNode** pphead, SLDatatype x)
{
assert(pphead);
SLNode* pcur = *pphead;
while (pcur)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
//出循環(huán),代表沒找到
return NULL;
}
//在指定位置之前插入
void SLNInsrt(SLNode** pphead, SLNode* pos, SLDatatype x)
{
SLNode* node = SLByNode(x);
assert(pphead);
assert(*pphead);
//只有一個節(jié)點的情況 == pos就是頭節(jié)點,直接進行頭插
if (pos == *pphead)
{
node->next = *pphead;
*pphead = node;
return;
}
//有多個節(jié)點的情況
//找pos的前一個節(jié)點
SLNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
//這里的插入節(jié)點的處理順序可以調(diào)換
prev->next = node;
node->next = pos;
}
//在指定的位置之后插入
void SLNInsrtAfter(SLNode* pos, SLDatatype x)
{
assert(pos);
SLNode* node = SLByNode(x);
// node pos node->next
node->next = pos->next;
pos->next = node;
}
//刪除指定位置的節(jié)點
void SLNErase(SLNode** pphead, SLNode* pos)
{
assert(pphead);
assert(*pphead);
assert(pos);
if (pos == *pphead)
{
*pphead = (*pphead)->next;
free(pos);
return;
}
//找pos的前一個節(jié)點
SLNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
//刪除指定位置之后的節(jié)點
void SLNEraseAfter(SLNode* pos)
{
assert(pos);
assert(pos->next);
SLNode* del = pos->next;
pos->next = del->next;
free(del);
del = NULL;
}
//銷毀鏈表
void SLNDestroy(SLNode** pphead)
{
assert(*pphead);
SLNode* pcur = *pphead;
while (pcur)
{
SLNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
2.3 單鏈表源文件——功能的測試
test.c
#include"SList.h"
void sllist()
{
SLNode* node1 = (SLNode*)malloc(sizeof(SLNode));
node1->data = 1;
SLNode* node2 = (SLNode*)malloc(sizeof(SLNode));
node2->data = 2;
SLNode* node3 = (SLNode*)malloc(sizeof(SLNode));
node3->data = 3;
SLNode* node4 = (SLNode*)malloc(sizeof(SLNode));
node4->data = 4;
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
SLNode* plist = node1;
SLNPrint(plist);
}
void sllist01()
{
SLNode* plist = NULL;
//尾插
SLNPushBack(&plist, 1);
SLNPushBack(&plist, 2);
SLNPushBack(&plist, 3);
SLNPushBack(&plist, 4);
//頭插
/*SLNPushFront(&plist, 1);
SLNPushFront(&plist, 2);
SLNPushFront(&plist, 3);
SLNPushFront(&plist, 4);*/
//尾刪
//SLNPopBack(&plist);
//頭刪
//SLNPopFront(&plist);
//在指定位置前插入
//查找指定的pos
SLNode* find = SLNFind(&plist, 3);
//SLNInsrt(&plist, find, 11);
//在指定位置之后插入
//SLNInsrtAfter(find, 5);
//刪除指定位置的節(jié)點
//SLNErase(&plist, find);
//刪除指定位置之后的節(jié)點
//SLNEraseAfter(find);
//銷毀鏈表
SLNDestroy(&plist);
SLNPrint(plist);//打印鏈表
}
2.4單鏈表測試結(jié)果運行展示
1.尾部插入
2.頭部插入
3.尾部/頭部刪除
4.在指定位置前插入
5.在指定位置后插入
6.刪除指定位置的節(jié)點
7.刪除指定位置之后的節(jié)點
8.銷毀鏈表
3. 鏈表的分類
鏈表的結(jié)構(gòu)?常多樣,以下情況組合起來就有8種(2 x 2 x 2)鏈表結(jié)構(gòu):
鏈表說明:
文章來源:http://www.zghlxwxcb.cn/news/detail-716470.html
雖然有這么多的鏈表的結(jié)構(gòu),但是我們實際中最常用還是兩種結(jié)構(gòu):
單鏈表和雙向帶頭循環(huán)鏈表
1. 無頭單向非循環(huán)鏈表:結(jié)構(gòu)簡單,?般不會單獨用來存數(shù)據(jù)。實際中更多是作為其他數(shù)據(jù)結(jié)構(gòu)的子結(jié)構(gòu),如哈希桶、圖的鄰接表等等。另外這種結(jié)構(gòu)在筆試面試中出現(xiàn)很多。
2. 帶頭雙向循環(huán)鏈表:結(jié)構(gòu)最復(fù)雜,?般用在單獨存儲數(shù)據(jù)。實際中使用的鏈表數(shù)據(jù)結(jié)構(gòu),都是帶頭雙向循環(huán)鏈表。另外這個結(jié)構(gòu)雖然結(jié)構(gòu)復(fù)雜,但是使?代碼實現(xiàn)以后會發(fā)現(xiàn)結(jié)構(gòu)會帶來很多優(yōu)勢,實現(xiàn)反而簡單了,后面我們代碼實現(xiàn)了就知道了。
文章來源地址http://www.zghlxwxcb.cn/news/detail-716470.html
到了這里,關(guān)于【(數(shù)據(jù)結(jié)構(gòu))— 單鏈表的實現(xiàn)】的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!