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

作者: 旋哥 | 来源:发表于2019-01-24 21:03 被阅读0次
    风景图风景图

    作者个人博客 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;
    }
    

    输出结果:


    输出结果输出结果

    相关文章

      网友评论

        本文标题:漫谈数据结构(二)——线性表2

        本文链接:https://www.haomeiwen.com/subject/xybodqtx.html