個人博客:點我
鏈表的基礎概念這里就不講了,隨便一個搜索引擎就能找到無數答案,我這里想講點別人不會講的東西,以及嘗試如何讓一竅不通於鏈表的同學快速理解和入門。
此處我們使用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;
}