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

《漫谈数据结构(二)——线性表2》 风景图

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

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

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

1、线性表的链式存储

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

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

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

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》 结点

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; 
}

《漫谈数据结构(二)——线性表2》 插入

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

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;
} 

《漫谈数据结构(二)——线性表2》 删除

  当前结点指定要删除位置的上一个结点(前结点),把前结点的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;
}

输出结果:

《漫谈数据结构(二)——线性表2》 输出结果

3、双链表实现链式存储

定义

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

《漫谈数据结构(二)——线性表2》 双链表

  双链表的实现和单链表类似,当我们插入一个新结点时,如果这个结点有后驱结点时,要是后驱结点的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;
} 

输出结果:

《漫谈数据结构(二)——线性表2》 输出结果

4、循环链表

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

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

《漫谈数据结构(二)——线性表2》 单向循环链表

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

《漫谈数据结构(二)——线性表2》 双循环链表

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

《漫谈数据结构(二)——线性表2》 多重循环链表

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

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

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;
}

输出结果:

《漫谈数据结构(二)——线性表2》 输出结果

    原文作者:旋哥
    原文地址: https://www.jianshu.com/p/582207ce647d
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞