漫談數(shù)據(jù)結(jié)構(gòu)(二)——線性表2

風景圖
風景圖

作者個人博客 https://www.you3xuan.top/ 查看原文。

本文為線性表第二篇慨亲,如果讀者想了解第一篇人弓,請點擊這里稍途。

源碼地址: https://github.com/ThinkingXuan/DataStructure </br>
如果對您有幫助,隨手一個Star吧倘潜。

1绷柒、線性表的鏈式存儲

??在鏈式存儲中,結(jié)點之間的內(nèi)存單元地址是不連續(xù)的涮因。它的每一個結(jié)點包括數(shù)據(jù)域和下一個結(jié)點的地址废睦。頭結(jié)點的數(shù)據(jù)域只存放結(jié)點的長度,并指向第一個元素养泡。尾結(jié)點指向NULL嗜湃。如圖所示:

鏈表
鏈表

??因為內(nèi)存單元不連續(xù),所以哪里空閑的內(nèi)存澜掩,都可以分配一個結(jié)點购披,提高了內(nèi)存的利用率。又因為結(jié)點之間只通過地址連接肩榕,所以刪除和插入結(jié)點效率高刚陡。又因為沒有索引與結(jié)點對應,查找一個結(jié)點的時候株汉,必須找到上一個結(jié)點筐乳,所以查詢效率不高。

2乔妈、鏈式存儲的實現(xiàn)

2.1 創(chuàng)建單鏈表

??分為三部分哥童,創(chuàng)建頭結(jié)點,創(chuàng)建普通結(jié)點褒翰,創(chuàng)建單鏈表贮懈。

  1. 創(chuàng)建頭結(jié)點
//創(chuàng)建頭結(jié)點匀泊,length存儲鏈表的長度 next指向下一個結(jié)點 
typedef struct Header{
    int length;
    struct Header * next;
}Head;
  1. 創(chuàng)建普通結(jié)點
//創(chuàng)建一個結(jié)點,data存放數(shù)據(jù)朵你,next指向下一個結(jié)點 
typedef struct Node{
    int data;
    struct Node *next; 
}ListNode;
  1. 創(chuàng)建鏈表
//創(chuàng)建一個鏈表各聘,返回頭結(jié)點 
Head * createList(){
    Head *phead = (Head*)malloc(sizeof(Head));
    phead->length = 0;
    phead->next = NULL;
    return phead;
}

2.2 獲取鏈表長度

// 獲取鏈表長度
int getLength(Head * phead){
    if(phead==NULL){
        printf("not init headnode!\n");
        return -1;
    }
    return phead->length;
} 

2.3 添加結(jié)點

// 添加數(shù)據(jù),,默認添加在末尾 
int addData(Head * phead, int data){
    if(phead==NULL){
        printf("not init head node!\n");
        return -1;
    }   
    
    //創(chuàng)建當前結(jié)點抡医,并指向鏈表最后一個結(jié)點
    ListNode * curNode = phead;
    while(curNode->next!=NULL){
        curNode = curNode->next;
    }
    
    //創(chuàng)建新結(jié)點 
    ListNode * newNode = (ListNode*)malloc(sizeof(ListNode));
    newNode->data = data;
    newNode->next = NULL;
    
    //連接結(jié)點
    curNode->next = newNode;
    phead->length++;
    return 1;       
} 

??如圖所示躲因,添加結(jié)點需要兩個結(jié)點,一個當前結(jié)點忌傻,指向尾結(jié)點大脉,另一個是要添加的新結(jié)點,指向NULL,使用當前結(jié)點的next指向新結(jié)點水孩,就完成了添加結(jié)點的操作镰矿。因為當前結(jié)點是指向尾結(jié)點的,當前結(jié)點的next就相當于尾結(jié)點的next俘种,所有就相當于尾結(jié)點的next指向了新結(jié)點秤标。最后別忘把頭結(jié)點的length加1。

結(jié)點
結(jié)點

2.4 插入結(jié)點

// 插入數(shù)據(jù) 
int insertData(Head * pHead, int data, int pos){
    if(pHead==NULL){
        printf("not init head node!\n");
        return -1;
    }
    if(pos < 0||pos > pHead->length){
        printf("insert postion error!\n");
        return -2;
    }
    
    //創(chuàng)建新結(jié)點 
    ListNode * newNode = (ListNode *)malloc(sizeof(ListNode));
    newNode->data = data;
    
    //創(chuàng)建當前結(jié)點
    ListNode * curNode = pHead;
    int i;
    for(i=0;i<pos;i++){
        curNode = curNode->next;    
    }
    newNode->next = curNode->next;
    curNode->next = newNode;    
    
    pHead->length++;
    
    return 1; 
}
插入
插入

??同樣宙刘,插入也需要兩個結(jié)點苍姜,一個結(jié)點指向要插入的位置的前一個結(jié)點,起名為當前結(jié)點悬包。另一個為新結(jié)點衙猪。主要就是兩行代碼:

newNode->next = curNode->next;
curNode->next = newNode;

??當前結(jié)點指向待插入位置的前一個結(jié)點,起名為前結(jié)點(lastNode)布近。以上代碼相當于:

newNode->next = lastNode->next;
lastNode->next = newNode;

??因為lastNode->next指向下一個結(jié)點∏停現(xiàn)在使用newNode->next指向下一個結(jié)點。然后使用lastNode->next指向newNode吊输。就完成了插入操作饶号。
??兩行代碼不可顛倒位置,因為先執(zhí)行第二行代碼的話季蚂,會導致后面結(jié)點全部丟失茫船。

2.5 刪除結(jié)點

// 刪除數(shù)據(jù) 
int deleteData(Head * pHead,int pos){
    if(pHead==NULL){
        printf("not init head node!\n");
        return -1;
    }
    if(pos < 0||pos >= pHead->length){
        printf("delete postion error!\n");
        return -2;
    }
    
    //創(chuàng)建當前結(jié)點
    ListNode * curNode = pHead;
    
    int i;
    for(i=0;i<pos;i++){
        curNode = curNode->next;    
    }
    
    curNode->next = curNode->next->next;
    pHead->length--;
    return 1;
} 

刪除
刪除

??當前結(jié)點指定要刪除位置的上一個結(jié)點(前結(jié)點),把前結(jié)點的next指向下一個結(jié)點的next,curNode->next = curNode->next->next,就完成了刪除操作扭屁。

2.6 獲取指定位置的結(jié)點

//獲取數(shù)據(jù) 
int getData(Head * pHead,int pos){
    if(pHead==NULL){
        printf("not init head node!\n");
        return -1;
    }
    if(pos < 0||pos >= pHead->length){
        printf("postion error!\n");
        return -2;
    }
    
    //創(chuàng)建當前結(jié)點
    ListNode * curNode = pHead;
    int i;
    for(i=0;i<=pos;i++){
        curNode = curNode->next;    
    }
    return curNode->data;
} 

2.7 打印所有結(jié)點

//打印 
void print(Head * phead){
    if(phead==NULL){
        printf("not init headnode!\n");
        return 0;
    }

    ListNode * node = phead->next;
    while(node->next!=NULL){
        printf("%d->",node->data);
        node = node->next;
    }
    printf("%d  length=%d\n",node->data,phead->length);
}

??為了讓打印效果更好算谈,想法去掉了最后一個->,并且輸出鏈表的長度料滥。

2.8 測試

#include<stdio.h>
#include<stdlib.h>

int main(){
    int i;
    printf("create ListNode:\n");
    Head* pHead = createList();
    printf("length=%d\n\n",pHead->length);
    
    printf("add data:\n");
    for(i=0;i<10;i++){
        addData(pHead,i);
    }
    print(pHead) ;
    printf("\n");
    
    printf("insert data:\n");
    insertData(pHead,100,3);
    print(pHead);
    printf("\n");
    
    printf("delete data:\n");
    deleteData(pHead,3);
    print(pHead);
    printf("\n");
    
    printf("get data:\n");
    printf("%d\n",getData(pHead,5));
    printf("\n");
    
    return 0;
}

輸出結(jié)果:

輸出結(jié)果
輸出結(jié)果

3然眼、雙鏈表實現(xiàn)鏈式存儲

定義

??前面使用單鏈表實現(xiàn)了線性表的鏈式存儲。但是單鏈表有個缺點葵腹,無法訪問前驅(qū)結(jié)點高每。當查找到一個元素結(jié)點時屿岂,如果想要找到前面的元素結(jié)點,需要從頭開始遍歷鲸匿,比較麻煩爷怀。所有雙鏈表有開辟了一個空間,存儲結(jié)點前驅(qū)結(jié)點的地址。如圖所示:

雙鏈表
雙鏈表

??雙鏈表的實現(xiàn)和單鏈表類似带欢,當我們插入一個新結(jié)點時运授,如果這個結(jié)點有后驅(qū)結(jié)點時,要是后驅(qū)結(jié)點的pre 指向新結(jié)點乔煞,新結(jié)點的pre也要指向它的前驅(qū)結(jié)點吁朦。其他操作類似,這里只貼出代碼渡贾,就不詳細解釋了逗宜。

3.1 創(chuàng)建雙鏈表

typedef struct Header{
    int length;
    struct Header * pre;  //為了方便,在頭結(jié)點添加一個pre 剥啤,不然無法指向 Node,在Head后面添加結(jié)點時就需要單獨判斷锦溪。 
    struct Header * next;
}Head;

typedef struct Node{
    int data;
    struct Node * pre;          
    struct Node * next;
}NodeList;

//創(chuàng)建 
Head * createDouNodeList(){
    Head * pHead = (Head*)malloc(sizeof(Head));
    if(pHead == NULL){
        printf("create failure!\n"); 
    } 
    pHead->length = 0;
    pHead->next = NULL;
    return pHead;
}

3.2 獲取鏈表長度

// 獲取鏈表長度
int getLength(Head * pHead){
    if(pHead==NULL){
        printf("not init head node!\n");
        return -1;
    }
    return pHead->length;
}

3.3 判斷是否為空

//判斷是否為空
int isEmpty(Head *pHead){
    if(pHead==NULL){
        printf("not init head node!\n");
        return -1;
    }
    if(pHead->length==0){
        return 1;
    }else{
        return 0;
    }
}

3.4 添加結(jié)點

// 添加結(jié)點不脯,,默認添加在末尾 
int addDataDou(Head * pHead, int data){
    if(pHead==NULL){
        printf("not init head node!\n");
        return -1;
    }   
    //創(chuàng)建當前結(jié)點府怯,并指向鏈表最后一個結(jié)點
    NodeList * curNode = pHead; 

    while(curNode->next != NULL){
        curNode = curNode->next;
    }

    //創(chuàng)建新結(jié)點 
    NodeList * newNode = (NodeList*)malloc(sizeof(NodeList));
    newNode->data = data; 
    newNode->next = NULL;
    
    curNode->next = newNode;
    newNode->pre = curNode;
    pHead->length++;
    return 1;
} 

3.5 插入結(jié)點

//插入 
int insertDou(Head *pHead,int data,int pos){
    
    if(pHead==NULL){
        printf("not init head node!\n");
        return -1;
    } 
    if(pos<=0||pos>=pHead->length){
        printf("insert positon error!\n");
        return -2;      
    }
    //創(chuàng)建新結(jié)點
    NodeList * newNode = (NodeList*)malloc(sizeof(NodeList));
    newNode->data = data;
     
    //創(chuàng)建當前結(jié)點,并指向 指定位置之前的那個結(jié)點 
    NodeList * curNode = pHead;
    int i;
    for(i=0;i<pos;i++){
        curNode = curNode->next;    
    }
    
    //連接 
    newNode->next = curNode->next;
    curNode->next->pre = newNode;
    newNode->pre = curNode;
    curNode->next = newNode;
    
    pHead->length++;
    
    return 1;
} 

3.6 刪除結(jié)點

//刪除 
int deleteDataDou(Head * pHead,int pos){
    if(pHead==NULL){
        printf("not init head node!\n");
        return -1;
    }
    if(pos < 0||pos >= pHead->length){
        printf("delete postion error!\n");
        return -2;
    }
    
    //創(chuàng)建當前結(jié)點
    NodeList * curNode = pHead;
    
    int i;
    for(i=0;i<pos;i++){
        curNode = curNode->next;    
    }
    
    curNode->next = curNode->next->next;
    
    //要刪除最后一個結(jié)點時判斷 
    if(curNode->next!=NULL){
        curNode->next->pre = curNode; 
    }
    
    pHead->length--;
    return 1;
} 

3.7 獲取指定元素的結(jié)點

//查找某個元素,返回它的結(jié)點 
NodeList * findNodeDou(Head *pHead,int val){
    if(pHead==NULL){
        printf("not init head node!\n");
        return 0;
    }
    NodeList *curNode = pHead->next;
    do{
        if(curNode->data == val){
            return curNode;
        }
        curNode = curNode->next;
        
    }while(curNode->next!=NULL);
    return NULL;
} 

3.8 打印所有結(jié)點

//打印 
void print(Head * pHead){
    if(pHead==NULL){
        printf("not init head node!\n");
        return 0;
    }
    NodeList * node = pHead->next;
    while(node->next!=NULL){
        printf("%d<->",node->data);
        node = node->next;
    }
    printf("%d  length=%d\n",node->data,pHead->length);
}

3.9 測試

//雙鏈表實現(xiàn)鏈式存儲
 #include <stdio.h>
 #include <stdlib.h>
 
int main(){
    int i;
    
    printf("Create Double Node List: \n"); 
    Head  *pHead =  createDouNodeList();
    printf("length = %d\n",pHead->length);
    printf("\n");
    
    printf("Add Data: \n");
    for(i=0;i<10;i++){
        addDataDou(pHead,i); 
    }
    print(pHead);
    printf("\n");   
    
    printf("Insert Data: \n");
    insertDou(pHead,100,3);
    print(pHead);
    printf("\n");   
    
    printf("delete Data: \n");
    deleteDataDou(pHead,3);
    print(pHead);
    printf("\n");
    
    printf("find Node: \n");
    NodeList * node = findNodeDou(pHead,3);
    printf("node is %d\n",node);
    printf("\n");
    
    return 0;
} 

輸出結(jié)果:

輸出結(jié)果
輸出結(jié)果

4防楷、循環(huán)鏈表

??鏈表還有一種常用的形式牺丙,那就是循環(huán)鏈表。循環(huán)鏈表首尾相接复局,形成一個環(huán)冲簿,從鏈表任何一個結(jié)點出發(fā),都能夠找到其他所有結(jié)點亿昏。

??循環(huán)鏈表分為單向循環(huán)鏈表峦剔,雙循環(huán)鏈表,多重循環(huán)鏈表角钩。如圖所示:

單向循環(huán)鏈表
單向循環(huán)鏈表

??上圖是單向循環(huán)鏈表吝沫,形成一個閉合環(huán),有一個方向递礼。


雙循環(huán)鏈表
雙循環(huán)鏈表

??上圖是雙向循環(huán)鏈表惨险,形成一個閉合環(huán),有兩個方向脊髓。


多重循環(huán)鏈表
多重循環(huán)鏈表

??上圖是多重循環(huán)鏈表辫愉,形成了兩個閉合環(huán)。

??本教程只講解單向循環(huán)鏈表将硝,其他兩種比較復雜恭朗,如需要的話屏镊,自行搜索。 循環(huán)鏈表的創(chuàng)建和查找基本和單鏈表一樣冀墨,這里就不過多講解了闸衫,只講解插入和刪除。如果您還不太清楚诽嘉,請認真閱讀前面的知識蔚出。

4.1 插入結(jié)點

int insertCircleList(Head * pHead,int data,int pos){
    if(pHead==NULL){
        printf("not init head node!\n");
        return -1;
    }
    if(pos < 0||pos > pHead->length){
        printf("insert postion error!\n");
        return -2;
    }
    
    //創(chuàng)建新結(jié)點 
    NodeList * newNode = (NodeList*)malloc(sizeof(NodeList));
    newNode->data = data;
    
    //如果是空 
    if(isEmpty(pHead)){
        pHead->next = newNode;   //直接插入到頭結(jié)點后面
        newNode->next = newNode; //自己指向自己 
    }else{
        
        NodeList *curNode = pHead->next;
        
        //因為pos ==0為涉及到頭結(jié)點,單獨處理 
        if(pos==0){
            
            //curNode指向尾結(jié)點
            while(curNode->next!=pHead->next){
                curNode = curNode->next;
            }
            
            newNode->next =pHead->next;
            pHead->next = newNode;
            curNode->next = newNode; 
        }else{
            //使curNode指向插入位置的前一個結(jié)點 
            int i;
            for(i=1;i<pos;i++){
                curNode = curNode->next;
            }
            
            newNode->next = curNode->next;
            curNode->next = newNode; 
            
        }
    } 
    
    pHead->length++;
    
    return 1;
}

4.2 刪除結(jié)點

int deleteCircleNode(Head *pHead,int pos){
    if(pHead==NULL){
        printf("not init head node!\n");
        return -1;
    }
    if(pos < 0||pos > pHead->length){
        printf("insert postion error!\n");
        return -2;
    }
    
    NodeList *curNode = pHead->next;
    
    if(isEmpty(pHead)){
        return -3;
    }else{
        if(pos==0){
            while(curNode->next!=pHead->next){
                curNode = curNode->next;
            } 
            
            curNode->next = curNode ->next->next;
            pHead->next = curNode ->next;
        }else{
            int i;
            for(i=1;i<pos;i++){
                curNode = curNode->next;
            }
            curNode ->next = curNode->next->next;
        }
    }
    
    pHead->length--;
    return 1; 
} 

4.3 其他代碼

//創(chuàng)建頭結(jié)點虫腋,length存儲鏈表的長度 next指向下一個結(jié)點 
typedef struct Header{
    int length;
    struct Header * next;
}Head;

//創(chuàng)建一個結(jié)點骄酗,data存放數(shù)據(jù),next指向下一個結(jié)點 
typedef struct Node{
    int data;
    struct Node *next; 
}NodeList;

//創(chuàng)建一個鏈表悦冀,返回頭結(jié)點 
Head * createList(){
    Head *phead = (Head*)malloc(sizeof(Head));
    phead->length = 0;
    phead->next = NULL;
    return phead;
}

//判斷是否為空
int isEmpty(Head *pHead){
    if(pHead==NULL){
        printf("not init head node!\n");
        return -1;
    }
    if(pHead->length==0){
        return 1;
    }else{
        return 0;
    }
}
//打印 
void print(Head *pHead){
    if(pHead==NULL){
        printf("not init headnode!\n");
        return 0;
    }

    NodeList * node = pHead->next;
    do{
        printf("%d->",node->data);
        node = node->next;
    }while(node!=pHead->next);

    printf("   length=%d\n",pHead->length);
}

4.4 測試

//循環(huán)鏈表
#include<stdio.h>
#include<stdlib.h>
int main(){
    int i;
    printf("Create Circle Node List: \n");
    Head * pHead = createList();
    printf("length = %d\n",pHead->length);
    printf("\n");
    
    printf("Insert Node: \n");
    for(i=0;i<11;i++){
        insertCircleList(pHead,i,i);
    }
    print(pHead);
    printf("\n");
    
    printf("Delete Node: \n");
    deleteCircleNode(pHead,0);
    print(pHead);
    return 0;
}

輸出結(jié)果:


輸出結(jié)果
輸出結(jié)果
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末趋翻,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子盒蟆,更是在濱河造成了極大的恐慌踏烙,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件历等,死亡現(xiàn)場離奇詭異讨惩,居然都是意外死亡,警方通過查閱死者的電腦和手機寒屯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門荐捻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人寡夹,你說我怎么就攤上這事处面。” “怎么了菩掏?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵魂角,是天一觀的道長。 經(jīng)常有香客問我智绸,道長野揪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任传于,我火速辦了婚禮囱挑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沼溜。我一直安慰自己平挑,他們只是感情好,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著通熄,像睡著了一般唆涝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上唇辨,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天廊酣,我揣著相機與錄音,去河邊找鬼赏枚。 笑死亡驰,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的饿幅。 我是一名探鬼主播凡辱,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼栗恩!你這毒婦竟也來了透乾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤磕秤,失蹤者是張志新(化名)和其女友劉穎乳乌,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體市咆,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡汉操,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了床绪。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片客情。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡其弊,死狀恐怖癞己,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情梭伐,我是刑警寧澤痹雅,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站糊识,受9級特大地震影響绩社,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜赂苗,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一愉耙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧拌滋,春花似錦朴沿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽魏铅。三九已至,卻和暖如春坚芜,著一層夾襖步出監(jiān)牢的瞬間览芳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工鸿竖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留沧竟,地道東北人。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓缚忧,卻偏偏與公主長得像屯仗,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子搔谴,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容