數據結構--鏈表入門超詳細解析(簡單易懂純原篇)


個人博客:點我

鏈表的基礎概念這里就不講了,隨便一個搜索引擎就能找到無數答案,我這里想講點別人不會講的東西,以及嘗試如何讓一竅不通於鏈表的同學快速理解和入門。

此處我們使用C進行演示


結點的創建

我們知道鏈表是由一個個結點串聯而成的,而每個結點分為兩塊區域,一塊是數據域,相當於數組中存儲的那個數據;另一塊是指針域,這里存放的是指向下一個結點的地址。在鏈表遍歷的過程中,我們根據前一個結點的指針域中的那個地址找到下一個結點,就這樣一個接一個往下遍歷,進行增刪改查等一系列基礎操作。

知道了結點的基礎結構就可以來進行創建了。

typedef struct lint{
  	// 數據域
    int score;
  	// 指針域
    struct lint* next; // 因為next是指向結點的,因此也要是結點的類型
}Lint;

這里使用typedef是為了重命名,省的以后創建指針時還要連帶着 struct lint*。之后創建指針就可以直接Lint* p而不是struct lint* p


鏈表初始化

鏈表中有一個特殊的結點–頭結點。頭結點的數據域一般不用來存放和其他節點同類的數據,它的誕生是為了在實際應用過程中存放一些數據(下面會演示到)。

我們先來進行無頭結點的初始化。

Lint * initLint(){
    // 創建頭指針
    Lint* p = NULL;
  	// 創建一個臨時指針初始化首元結點,並且用於移動和調整后續結點
	Lint* temp = (Lint*)malloc(sizeof(Lint));
  	temp->score = 90;
  	temp->next = NULL;
  	// 頭指針需要指向首元結點,這樣才能定位這串鏈表的位置
  	p = temp;
  	// 手動創建10個元素
    for(int i = 0;i < 10;i++){
        // 從第二個結點開始創建
        Lint * a = (Lint*) malloc(sizeof(Lint));
        a->score = i + 91;
        a->next = NULL;
        // 將當前temp指向的結點的next指向下一個結點
        temp->next = a;
        // temp移到下一個結點。這樣在下次循環運行到上一行(line17)時能持續
        temp = temp->next;  // 或 temp = a;
    }
    return p;
}

再來看有頭結點的初始化方式(由於存在頭結點稍微有些復雜,因此后續的演示會采用有頭結點的鏈表):

Lint * initLint(){
    // 創建頭指針,並用其創建頭結點
    Lint * p = (Lint*)malloc(sizeof (Lint));
    // 及時指定指針的指向以確保指針安全
    p->next = NULL;
    // 創建一個臨時的指針指向頭結點以進行后續的操作
    Lint * temp = p;
    for(int i = 0;i < 10;i++){
        // 從首元結點開始創建
        Lint * a = (Lint*) malloc(sizeof(Lint));
        a->score = i + 90;
        a->next = NULL;
        // 將當前temp指向的next指向下一個結點
        temp->next = a;
        // temp移到下一個結點
        temp = temp->next;  // 或 temp = a;
    }
    return p;
}

鏈表的輸出

現在我們已經完成了整條鏈表的手動賦值初始化,最基礎的當然就是輸出鏈表了。

基本的思想非常簡單,頭指針已經指明了鏈表的頭,利用指針輸出結點的數據,然后根據指針域中的地址移到下一個結點,循環往復,一直到指針域為空就停止輸出。

void showLint(Lint* p){	
  	// 一開始p指針在頭結點,這個數據域是空的,因此需要檢測其指針域中是否有下一個節點的地址。如果為空,則退出循環
    while (p->next){
      	// p從頭結點移出,到首元結點開始輸出
        p = p->next;
        printf("%d\t", p->score);
    }
    printf("\n");
}

運行結果:


基礎操作

1. 增

增加結點的邏輯是:將新增結點的指針域指向后一個結點,然后將原鏈表中的前一個結點的指針域指向新增的結點。(注意順序不能顛倒,否則會導致插入位置后面的結點全部丟失)

Lint* insertLint(Lint* p,int n, int num){		// 將num插入到第n個位置
    Lint * temp = p;
    // 通過遍歷將temp指針移到指向插入位置的直接前驅結點的位置
    for (int i = 1;i < n;i++){
        // 防止超過鏈表現有長度
        if (temp->next == NULL){
            printf("位置錯誤!\n");
            return p;
        }
        temp = temp->next;
    }
    // 創建插入的新結點
    Lint * a = (Lint*) malloc(sizeof (Lint));
    a->score = num;
  	// 新節點數據域指向原位置后一個結點
    a->next = temp->next;
  	// temp后移一個結點
    temp->next = a;
    return p;
}

主函數中調用

printf("請輸入插入的位置和數字,用空格隔開:");
    scanf("%d%d", &n, &num);
    insertLint(p, n, num);
    showLint(p);

運行結果:

2. 刪

邏輯和增差不多,將temp指針指向要刪除結點的直接前驅,將指針域指向下下個結點,然后釋放刪除結點的內存即可。

Lint* delLint(Lint* p,int n){
    Lint * temp = p;
    // 用於存儲臨時刪除的結點
    Lint * back = NULL;
    // temp移到刪除結點的直接前驅結點
    for (int i = 1;i < n;i++){
        // 防止越過鏈表
        if (temp->next == NULL){
            printf("位置錯誤!\n");
            return p;
        }
        temp = temp->next;
    }
    // 臨時存儲被刪除結點,防止丟失
    back = temp->next;
  	// 指向下下個結點
    temp->next = temp->next->next;
    // 手動釋放內存防止泄露
    free(back);
    return p;
}

在主函數中調用:

printf("請輸入刪除第幾個元素:");
scanf("%d", &n);
delLint(p, n);
showLint(p);

運行結果:

3. 改

這個邏輯更簡單,指針指向要修改的結點,直接修改其score值

Lint* changeLint(Lint* p,int n, int num){  	
	// temp可以一開始就指向首元結點 
	Lint * temp = p->next;    
	for (int i = 1;i < n;i++){        
	// 防止越過鏈表 
		if (temp->next == NULL){            
			printf("位置錯誤!\n");            
			return p;        
		}        
		temp = temp->next;    
	}    
	// 修改 
	temp->score = num;    
	return p;
}

運行結果:

4. 查

查的基本思想是指針進行遍歷鏈表,對每個節點的數據域進行對比,如果相同就可以直接退出返回結果了。如果一直到鏈表結束還沒有匹配成功就說明沒有找到。

int searchLint(Lint* p, int num){
    Lint * temp = p;
    int i;  // 用於記數
    for(i = 1;temp->next != NULL;i++){
      	// 因為是從頭結點開始的,因此要先移到首元結點
        temp = temp->next;
        if (temp->score == num)return i;	// 返回i表示找到的位置
    }
    return 0;	// 返回0表示未找到
}

當然也可以將頭結點的數據域用於存儲位置,而無需單獨創建一個i用於記錄位置。

int searchLint(Lint* p, int num){
    Lint * temp = p;
    for(p->score = 1;temp->next != NULL;p->score++){
        temp = temp->next;
        if (temp->score == num)return p->score;
    }
    return 0;
}

主函數進行調用

printf("請輸入需要查詢的分數:");
scanf("%d", &num);
if (!searchLint(p, num))
  printf("未查詢到該分數");
else
  printf("在第%d個元素", searchLint(p, num));

運行結果如下:


完整代碼

#include "stdio.h"
#include "stdlib.h"
typedef struct lint{
    int score;
    struct lint* next;
}Lint;
Lint * initLint(){
    Lint * p = (Lint*)malloc(sizeof (Lint));
    p->next = NULL;
    Lint * temp = p;
    for(int i = 0;i < 10;i++){
        Lint * a = (Lint*) malloc(sizeof(Lint));
        a->score = i + 90;
        a->next = NULL;
        temp->next = a;
        temp = temp->next;
    }
    return p;
}
void showLint(Lint* p){
    while (p->next){
        p = p->next;
        printf("%d\t", p->score);
    }
    printf("\n");
}

// 增
Lint* insertLint(Lint* p,int n, int num){
    Lint * temp = p;
    for (int i = 1;i < n;i++){
        if (temp->next == NULL){
            printf("位置錯誤!\n");
            return p;
        }
        temp = temp->next;
    }
    Lint * a = (Lint*) malloc(sizeof (Lint));
    a->score = num;
    a->next = temp->next;
    temp->next = a;
    return p;
}

// 刪
Lint* delLint(Lint* p,int n){
    Lint * temp = p;
    Lint * back = NULL;
    for (int i = 1;i < n;i++){
        if (temp->next == NULL){
            printf("位置錯誤!\n");
            return p;
        }
        temp = temp->next;
    }
    back = temp->next;
    temp->next = temp->next->next;
    free(back);
    return p;
}

// 改
Lint* changeLint(Lint* p,int n, int num){
    Lint * temp = p->next;
    for (int i = 1;i < n;i++){
        if (temp->next == NULL){
            printf("位置錯誤!\n");
            return p;
        }
        temp = temp->next;
    }
    temp->score = num;
    return p;
}

// 查(頭結點存儲位置)
int searchLint(Lint* p, int num){
    Lint * temp = p;
    for(p->score = 1;temp->next != NULL;p->score++){
        temp = temp->next;
        if (temp->score == num)return p->score;
    }
    return 0;
}

int main(){
    int n, num;
    Lint* p = initLint();
    showLint(p);
    // 增
    printf("請輸入插入的位置和數字,用空格隔開:");
    scanf("%d%d", &n, &num);
    insertLint(p, n, num);
    showLint(p);
    // 刪
    printf("請輸入刪除第幾個元素:");
    scanf("%d", &n);
    delLint(p, n);
    showLint(p);
    // 改
    printf("請輸入修改第幾個元素為什么數,空格隔開:");
    scanf("%d%d", &n, &num);
    changeLint(p, n, num);
    showLint(p);
    // 查
    printf("請輸入需要查詢的分數:");
    scanf("%d", &num);
    if (!searchLint(p, num))
        printf("未查詢到該分數");
    else
        printf("在第%d個元素", searchLint(p, num));
}

約瑟夫問題

//約瑟夫問題:n 個人圍成一個圓圈,首先第1個人開始,一個人一個人順時針報數, 報到第m個人,令其出列;然后再從下一個人開始,從1順時針報數,報到第m個人,再令其出列,…,如此下去, 直到圓圈中只剩一個人時,此人即為優勝者。
//例如:n=8,m=3
#include "stdio.h"
#include "stdlib.h"
typedef struct lint{
    int num;
    struct lint* next;
}Lint;
// 帶有頭結點的鏈表初始化
Lint* initLint(int n){
    Lint* p = (Lint*) malloc(sizeof(Lint));
    Lint* temp = p;
    p->next = NULL;
    for (int i = 0; i < n; i++) {
        Lint* a = (Lint*) malloc(sizeof(Lint));
        a->num = i+1;
        a->next = NULL;
        temp->next = a;
        temp = a;
    }
    // 循環鏈表
    temp->next = p->next;
    return p;
}
// 實現功能函數
void Func(Lint* p, int m){
    Lint* temp = p;
    // 就剩下自己一個結點
    while(temp->next != temp->next->next){
        for (int i = 0;i < m-1;i++)temp = temp->next;
        printf("%d\t", temp->next->num);
        // 刪除結點
        temp->next = temp->next->next;
    }
    printf("%d", temp->num);
}
int main(){
    int n, m;
    printf("請輸入n和m的值(用空格隔開):");
    scanf("%d%d", &n, &m);
    if (n >= m){
        Lint* p = initLint(n);
        Func(p, m);
    }else
        printf("輸入錯誤!");
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM