漫谈数据结构(二)——线性表2

风景图
风景图

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

本文为线性表第二篇,如果读者想了解第一篇,请点击这里

源码地址: https://github.com/ThinkingXuan/DataStructure </br>
如果对您有帮助,随手一个Star吧。

1、线性表的链式存储

  在链式存储中,结点之间的内存单元地址是不连续的。它的每一个结点包括数据域和下一个结点的地址。头结点的数据域只存放结点的长度,并指向第一个元素。尾结点指向NULL。如图所示:

链表
链表

  因为内存单元不连续,所以哪里空闲的内存,都可以分配一个结点,提高了内存的利用率。又因为结点之间只通过地址连接,所以删除和插入结点效率高。又因为没有索引与结点对应,查找一个结点的时候,必须找到上一个结点,所以查询效率不高。

2、链式存储的实现

2.1 创建单链表

  分为三部分,创建头结点,创建普通结点,创建单链表。

  1. 创建头结点
//创建头结点,length存储链表的长度 next指向下一个结点 
typedef struct Header{
    int length;
    struct Header * next;
}Head;
  1. 创建普通结点
//创建一个结点,data存放数据,next指向下一个结点 
typedef struct Node{
    int data;
    struct Node *next; 
}ListNode;
  1. 创建链表
//创建一个链表,返回头结点 
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 添加结点

// 添加数据,,默认添加在末尾 
int addData(Head * phead, int data){
    if(phead==NULL){
        printf("not init head node!\n");
        return -1;
    }   
    
    //创建当前结点,并指向链表最后一个结点
    ListNode * curNode = phead;
    while(curNode->next!=NULL){
        curNode = curNode->next;
    }
    
    //创建新结点 
    ListNode * newNode = (ListNode*)malloc(sizeof(ListNode));
    newNode->data = data;
    newNode->next = NULL;
    
    //连接结点
    curNode->next = newNode;
    phead->length++;
    return 1;       
} 

  如图所示,添加结点需要两个结点,一个当前结点,指向尾结点,另一个是要添加的新结点,指向NULL,使用当前结点的next指向新结点,就完成了添加结点的操作。因为当前结点是指向尾结点的,当前结点的next就相当于尾结点的next,所有就相当于尾结点的next指向了新结点。最后别忘把头结点的length加1。

结点
结点

2.4 插入结点

// 插入数据 
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;
    }
    
    //创建新结点 
    ListNode * newNode = (ListNode *)malloc(sizeof(ListNode));
    newNode->data = data;
    
    //创建当前结点
    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; 
}
插入
插入

  同样,插入也需要两个结点,一个结点指向要插入的位置的前一个结点,起名为当前结点。另一个为新结点。主要就是两行代码:

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

  当前结点指向待插入位置的前一个结点,起名为前结点(lastNode)。以上代码相当于:

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

  因为lastNode->next指向下一个结点。现在使用newNode->next指向下一个结点。然后使用lastNode->next指向newNode。就完成了插入操作。
  两行代码不可颠倒位置,因为先执行第二行代码的话,会导致后面结点全部丢失。

2.5 删除结点

// 删除数据 
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;
    }
    
    //创建当前结点
    ListNode * curNode = pHead;
    
    int i;
    for(i=0;i<pos;i++){
        curNode = curNode->next;    
    }
    
    curNode->next = curNode->next->next;
    pHead->length--;
    return 1;
} 

删除
删除

  当前结点指定要删除位置的上一个结点(前结点),把前结点的next指向下一个结点的next,curNode->next = curNode->next->next,就完成了删除操作。

2.6 获取指定位置的结点

//获取数据 
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;
    }
    
    //创建当前结点
    ListNode * curNode = pHead;
    int i;
    for(i=0;i<=pos;i++){
        curNode = curNode->next;    
    }
    return curNode->data;
} 

2.7 打印所有结点

//打印 
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;
}

输出结果:

输出结果
输出结果

3、双链表实现链式存储

定义

  前面使用单链表实现了线性表的链式存储。但是单链表有个缺点,无法访问前驱结点。当查找到一个元素结点时,如果想要找到前面的元素结点,需要从头开始遍历,比较麻烦。所有双链表有开辟了一个空间,存储结点前驱结点的地址。如图所示:

双链表
双链表

  双链表的实现和单链表类似,当我们插入一个新结点时,如果这个结点有后驱结点时,要是后驱结点的pre 指向新结点,新结点的pre也要指向它的前驱结点。其他操作类似,这里只贴出代码,就不详细解释了。

3.1 创建双链表

typedef struct Header{
    int length;
    struct Header * pre;  //为了方便,在头结点添加一个pre ,不然无法指向 Node,在Head后面添加结点时就需要单独判断。 
    struct Header * next;
}Head;

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

//创建 
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 添加结点

// 添加结点,,默认添加在末尾 
int addDataDou(Head * pHead, int data){
    if(pHead==NULL){
        printf("not init head node!\n");
        return -1;
    }   
    //创建当前结点,并指向链表最后一个结点
    NodeList * curNode = pHead; 

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

    //创建新结点 
    NodeList * newNode = (NodeList*)malloc(sizeof(NodeList));
    newNode->data = data; 
    newNode->next = NULL;
    
    curNode->next = newNode;
    newNode->pre = curNode;
    pHead->length++;
    return 1;
} 

3.5 插入结点

//插入 
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;      
    }
    //创建新结点
    NodeList * newNode = (NodeList*)malloc(sizeof(NodeList));
    newNode->data = data;
     
    //创建当前结点,并指向 指定位置之前的那个结点 
    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 删除结点

//删除 
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;
    }
    
    //创建当前结点
    NodeList * curNode = pHead;
    
    int i;
    for(i=0;i<pos;i++){
        curNode = curNode->next;    
    }
    
    curNode->next = curNode->next->next;
    
    //要删除最后一个结点时判断 
    if(curNode->next!=NULL){
        curNode->next->pre = curNode; 
    }
    
    pHead->length--;
    return 1;
} 

3.7 获取指定元素的结点

//查找某个元素,返回它的结点 
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 打印所有结点

//打印 
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 测试

//双链表实现链式存储
 #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;
} 

输出结果:

输出结果
输出结果

4、循环链表

  链表还有一种常用的形式,那就是循环链表。循环链表首尾相接,形成一个环,从链表任何一个结点出发,都能够找到其他所有结点。

  循环链表分为单向循环链表,双循环链表,多重循环链表。如图所示:

单向循环链表
单向循环链表

  上图是单向循环链表,形成一个闭合环,有一个方向。


双循环链表
双循环链表

  上图是双向循环链表,形成一个闭合环,有两个方向。


多重循环链表
多重循环链表

  上图是多重循环链表,形成了两个闭合环。

  本教程只讲解单向循环链表,其他两种比较复杂,如需要的话,自行搜索。 循环链表的创建和查找基本和单链表一样,这里就不过多讲解了,只讲解插入和删除。如果您还不太清楚,请认真阅读前面的知识。

4.1 插入结点

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;
    }
    
    //创建新结点 
    NodeList * newNode = (NodeList*)malloc(sizeof(NodeList));
    newNode->data = data;
    
    //如果是空 
    if(isEmpty(pHead)){
        pHead->next = newNode;   //直接插入到头结点后面
        newNode->next = newNode; //自己指向自己 
    }else{
        
        NodeList *curNode = pHead->next;
        
        //因为pos ==0为涉及到头结点,单独处理 
        if(pos==0){
            
            //curNode指向尾结点
            while(curNode->next!=pHead->next){
                curNode = curNode->next;
            }
            
            newNode->next =pHead->next;
            pHead->next = newNode;
            curNode->next = newNode; 
        }else{
            //使curNode指向插入位置的前一个结点 
            int i;
            for(i=1;i<pos;i++){
                curNode = curNode->next;
            }
            
            newNode->next = curNode->next;
            curNode->next = newNode; 
            
        }
    } 
    
    pHead->length++;
    
    return 1;
}

4.2 删除结点

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 其他代码

//创建头结点,length存储链表的长度 next指向下一个结点 
typedef struct Header{
    int length;
    struct Header * next;
}Head;

//创建一个结点,data存放数据,next指向下一个结点 
typedef struct Node{
    int data;
    struct Node *next; 
}NodeList;

//创建一个链表,返回头结点 
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 测试

//循环链表
#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;
}

输出结果:


输出结果
输出结果
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容

  • 本文内容取自于小甲鱼的数据结构与算法。//www.greatytc.com/p/230e6fde9c75 ...
    阿阿阿阿毛阅读 2,889评论 0 7
  • 1.线性表的定义 线性表:零个或多个数据元素的有限序列序列:也就是说元素之间是有顺序的,若元素存在多个,则第一个元...
    e40c669177be阅读 2,058评论 6 15
  • 数据结构与算法-目录 1、线性表的链式存储结构 1.1、线性表链式存储结构定义 线性表的链式存储结构的特点是用一组...
    香沙小熊阅读 1,757评论 1 1
  • 谁都说近乡情又却 却都说谁能却情近乡 乡愁仿佛是晨起时的闹钟 定期骚扰你 却被你定期地关闭 出乡时听的歌谣还依稀记...
    冰棍君阅读 248评论 0 0
  • 愿望:今天下午6点到7点我能够去运动 结果:如果我运动了,我就能瘦下来 障碍:时间问题,天气问题 计划:抓紧一切时...
    Huifre7阅读 110评论 0 0