精华内容
下载资源
问答
  • 主要介绍了C语言中双向链表和双向循环链表详解的相关资料,需要的朋友可以参考下
  • C++实现的带头结点的双向循环链表, 数据结构课设.。
  • 循环双链表与双循环链表差别Solution: 解: Input: The data of each node 输入:每个节点的数据 Output: The circular doubly linked list 输出:圆形双向链表 Data structure used: A circular doubly ...

    循环双链表与双循环链表差别

    Solution:

    解:

    Input: The data of each node

    输入:每个节点的数据

    Output: The circular doubly linked list

    输出:圆形双向链表

    Data structure used: A circular doubly linked list where each node contains a data part, say data and two link parts say prev (to store the address of immediate previous node) and say next (to store the address of immediate next node).

    使用的数据结构:环形双链表,其中每个节点包含一个数据部分,例如data和两个链接部分说prev (存储紧邻的前一个节点的地址)和next (存储紧邻的下一个节点的地址)。

    Algorithm:

    算法:

    Begin
    head=NULL
    //get a memory block of type node and store it in the pointer temp
    temp=getnode(node)		
    If(temp!=NULL)
    	Print("Enter the element in the list : ",temp->data)
    	temp->next=head;
    	If(head=NULL)
    		head=temp
    		temp->prev=head
    		temp->next=head
    	Else
    		temp1=head;
    		while(temp1->next!=head)
    			begin
    			temp1=temp1->next;
    		End while
    			temp1->next=temp;
    			temp->prev=temp1;
    	End IF-Else	
    Else
    	Printf ("Memory not avilable...node allocation is not possible")
    End IF-ELSE
    End
    
    

    C code:

    C代码:

    #include <stdio.h>
    #include <stdlib.h>
    
    //cicular double linked list structure
    typedef struct list 
    {
    	int data; 
    	struct list *next;
    	struct list *prev;
    }node;
    
    void display(node *temp){
    	//now temp1 is head basically
    	node *temp1=temp; 
    	printf("The list is as follows :\n%d->",temp->data);
    	temp=temp->next;
    	//which not circle back to head node 
    	while(temp!=temp1) 
    	{
    		printf("%d->",temp->data);
    		temp=temp->next;
    	}
    	printf("%d\n",temp1->data);
    	return;
    }
    
    int main(){
    	node *head=NULL,*temp,*temp1;
    	int choice;
    	
    	//building circular double linked list as input
    	do
    	{
    		temp=(node *)malloc(sizeof(node));
    		if(temp!=NULL)
    		{
    			printf("\nEnter the element in the list : ");
    			scanf("%d",&temp->data);
    			temp->next=head;
    			if(head==NULL)
    			{	
    				head=temp;
    				temp->prev=head;
    				temp->next=head;
    			}
    			else
    			{
    				temp1=head;
    				while(temp1->next!=head)
    				{
    					temp1=temp1->next;
    				}
    				temp1->next=temp;
    				temp->prev=temp1;
    			}
    		}
    		else
    		{
    		printf("\nMemory not avilable...node allocation is not possible");
    		}
    		printf("\nIf you wish to add more data on the list enter 1 : ");
    		scanf("%d",&choice);
    	}while(choice==1);
    	
    	display(head);
    	
    	return 0;
    }
    
    

    Output

    输出量

    Enter the element in the list : 1
    
    If you wish to add more data on the list enter 1 : 1
    
    Enter the element in the list : 2
    
    If you wish to add more data on the list enter 1 : 1
    
    Enter the element in the list : 3
    
    If you wish to add more data on the list enter 1 : 1
    
    Enter the element in the list : 4
    
    If you wish to add more data on the list enter 1 : 1
    
    Enter the element in the list : 5
    
    If you wish to add more data on the list enter 1 : 0
    The list is as follows :
    1->2->3->4->5->1
    
    
    

    翻译自: https://www.includehelp.com/c-programs/implement-circular-doubly-linked-list.aspx

    循环双链表与双循环链表差别

    展开全文
  • 本篇文章主要介绍了JavaScript数据结构之双向链表和双向循环链表的实现,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 双向链表和循环链表的区别

    千次阅读 2020-11-14 11:01:03
    双向链表: 1、为了方便编程,以及节点的插入删除,可以在首尾增加一个空节点,方便一系列的操作判断,最后一个节点指针指向空数据的节点或者指向空。 2、双向链表在访问元素时,可以从任何结点开始...3、循环链表

    双向链表:

    1、为了方便编程,以及节点的插入删除,可以在首尾增加一个空节点,方便一系列的操作和判断,最后一个节点指针指向空数据的节点或者指向空。

    2、双向链表在访问元素时,可以从任何结点开始任意向前向后双向访问节点元素,不像循环单链表只能按顺序访问后节点。

    3、因为有前后两个指针,双向链表删除前驱和后驱节点,以及插入节点的操作非常便捷。
    在这里插入图片描述

    循环链表:

    1、循环链表没有尾指针,必须将最后的一个节点的后指针指向头节点

    2、循环链表在访问元素时,可以从任何一个结点开始,但是只能顺序向后访问节点元素

    3、循环链表在某个节点的前面插入节点时,需要先找到改节点的前驱节点,在进行操作,不像双向链表一样,可以在当前节点访问前驱节点,没有双向链表操作方便。
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/20201114104048513.png#pic_center

    展开全文
  • 若是不清楚链表的结构,该篇文章不适合观看,这里只做文字说明,没有链表结构的图示


    前言:
    线性表是我们最常用的一种数据结构,线性表包含顺序表和链表,顺序表典型应用就是我们常用的ArrayList,链表的典型应用其中就有我们常用的LinkedList。LinkedList他的底层就是使用链表来存储数据元素的。这篇文章用以总结链表中的双向循环链表,为单链表的结点增加一个指向前驱的指针域,单链表就变成了双链表,将双链表的头尾相连,双链表就成了双向循环链表。

    一、相关概念

    第一部分主要介绍下和链表有关的相关概念,这里给出的概念都是书本上的官方定义,并不是作者胡诌的,为什么给出这些官方的定义呢 ?因为笔者认为官方给出的定义,是对一个概念最本质的解释,它的解释经过了时间的考验,被认为是解释的最合理深入简明扼要的。

    1.什么是线性表

    线性表是由n个数据元素构成的有限序列,n为线性表的表长,当n为0时表示当前线性表是空表。线性表有且仅有一个开始结点和终端节点。且开始结点没有前驱,终端节点没有后继,其余节点有且仅有一个前驱和后继。线性表一般分为顺序表和链表。

    2.什么是顺序表

    采用顺序存储结构存储数据元素的线性表被称为顺序表,顺序表,要求逻辑上相邻的数据元素在物理内存的存储上也是相邻的,当在顺序表的第i(0<=i<=n)插入一个数据元素时,需要后移n-i+1个数据元素,当删除第i个元素时需要移动n-i个数据元素。java实现顺序表

    3.什么是链表

    采用链式存储结构存储数据元素的线性表被称为链表,链表不要求逻辑上相邻的数据元素内存上必须相邻,链表的每个节点都包含两部分,一部分是数据域用以存储数据,一部分是指针域用以存储指向相邻结点的指针或者引用。链表通过每个节点的指针域将一串数据串联成链。当结点只有一个指针域时,被称为单链表。单链表-含头结点单链表-不含头结点

    4.单链表、双链表、循环单链表、循环双链表

    当链表的结点只有一个指针域时被称为单链表,循环单链表是单链表的一种特殊变化,只是将尾结点又指向了头结点(也可能是首结点)。
    当链表的结点有两个时,一个指向前驱,一个指向后继这种链表便是双链表,循环双链表是双链表的一种特殊变化,只是将尾结点又指向了头结点(也可能是首结点)。
    所有的链表的实现均有两种方式,一种是带有头结点,一种是不带有头结点的实现方式,两种实现略有区别。

    5.为什么需要循环链表?

    循环链表一般就是指循环单链表,不特殊指明是双向循环链表,那么该名称描述的就是单项循环链表,之所以需要循环链表是因为在操作系统中,循环链表有特定的应用场景,在一些场景中,链表中的元素需要循环的执行,但是在实际的开发中应用最多的还是双向循环链表。

    6.为什么需要双向链表?

    若是给定了一个结点,根据当前结点获取该结点的上一个结点,那么我们是没有办法直接获取的,只能是遍历链表,但若是使用了双向链表,我们则可以直接获取到该元素的上一个结点,时间复杂度就变成了O(1)。所以双向链表的实际应用意义更强,将双向链表的首尾相连就成了双向循环链表,双向循环链表的应用最常见的就是LinkedList。

    7.头结点和首结点

    首节点:真正存储第一个数据元素的结点被称为首节点。
    头结点:当链表采用带头结点方式实现时,会创建一个数据域为null的节点,该节点的指针域存储的指针指向首节点的指针,他的唯一作用是标识链表的开始,带有头结点的链表更易于操作。

    8.常见的栈和队列与线性表的关系

    栈和队列也是常见的数据结构,但是栈和队列并不是线性表的一种,线性表只包含了顺序表和链表。不过我们可以将栈和队列看成是特殊的线性表。怎么特殊呢,栈是被限制了只能在一端进行插入和删除操作的线性表,所以栈的特性是先入后出,队列是被限制了只能在一端插入另一端删除的线性表,所以队列的特性是先入先出。既然栈和队列都是特殊的线性表,那么栈和队列自然就可以使用线性表来实现了,通常可以使用线性表中的顺序表和队列来实现栈和队列。

    二、实现过程

    单链表-含头结点
    单链表-不含头结点
    上面是两种单链表的实现方式,其实无论是双链表还是双向循环链表他的实现方式都是类似的,区别都很有限,上面两篇文章里详细实现了单链表的各种常用方法,因此在该文章里只会总结必须用到的几个方法,比如插入、删除、清空、长度、判空、根据下标获取等,其他方法的实现都不难,就不一一展示了。

    1.提供节点类:DupNode

    双向循环链表的实现,我们首先必须为其提供一个结点类,该类需要有三个属性,一个数据域,一个指向前驱的指针域,还有一个指向后继的指针域,然后提供必要的构造方法即可,如下:

    /**
     * @author pcc
     * @version 1.0.0
     * @className DupNode
     * @date 2021-04-19 16:43
     * 该类是双向链表的结点类
     * 该类包含了数据域,后继指针域、前驱指针域三个属性。
     */
    public class DupNode {
        Object object;
        DupNode prior;//前驱指针域
        DupNode next;//后继指针域
    
        public DupNode(){
            this(null);
        }
        public DupNode(Object object){
            this(object,null,null);
        }
        public DupNode(Object object,DupNode prior,DupNode next){
            this.object = object;
            this.prior = prior;
            this.next = next;
        }
    }
    

    2.提供双向循环链表的实现类:DoubleLinkedTable

    采用带有头结点的方式实现双向循环链表,因此我们需要定义一个头结点。然后提供初始化它的构造器。值得注意的是,在初始化头结点时,我们必须将他的前驱和后继都声明为自己,这样才是一个空的双向循环链表。

    public class DoubleLinkedTable {
        //头结点
        DupNode head;
    
        public DoubleLinkedTable(){
            head = new DupNode();
            head.prior = head;
            head.next = head;
        }
    }
    

    3.提供长度(length)、打印(display)、清空(clear)等方法

    这些方法的实现原理都很简单,求链表长就是遍历链表计数即可,打印也是遍历链表,清空则是将头结点的前驱和后继都声明为自己,下面是三个方法的实现:

        //长度
        public int length(){
            DupNode node = head.next;
            int j = 0;
            while(!node.equals(head)){
                j++;
                node = node.next;
            }
            return j;
        }
    
        //打印
        public void display(){
            DupNode node = head.next;
            while(!node.equals(head)){
                System.out.println(node.object);
                node = node.next;
            }
        }
        //清空
        public void clear(){
            head.next = head;
            head.prior = head;
        }
    

    4.提供根据下标插入方法:insert(int i,Object object)
    学习双向循环链表建议还是先学习单链表,会了单链表双向循环链表就是窗户纸,一桶就破,因为他们的实现思路都是一样的,只是稍微的变化而已。单链表的遍历我们的退出条件是找到尾结点就退出(node.next == null),循环链表肯定没有尾结点了,退出循环的条件就成了碰到头结点再退出(node ==head),另外一点区别就是双向循环链表的赋值问题,我们需要为新结点的前驱指针和后继指针赋值,同时需要为新结点的上一个节点的后继后继指针从新赋值,还需要为新节点的后继结点的前驱指针重新赋值,代码实现如下:

       /**
         * 思路:
         * 1.寻找下标为i-1的数据元素,注意退出循环的条件应该是node!=head
         * 2.赋值即可,循环链表的核心就是空表也会有循环体系
         * 3,赋值时,i+1位置的元素应该是node.next 所以,应为node.next最后赋值
         * @param i
         * @param object
         * @throws Exception
         */
        public void insert(int i,Object object) throws Exception{
            if(i<0 || i>length())
                throw new Exception("下标不合法");
             DupNode node = head;
             int j = 0;
             while(!node.next.equals(head) && j<i){
                 j++;
                 node = node.next;
             }
    //         DupNode newNode = new DupNode(object);
    //         node.next.prior = newNode;
    //         newNode.prior = node;
    //         newNode.next = node.next;
    //         node.next = newNode;
    
            //写成以下这种和上面注释的部分,效果一样,无区别
             DupNode newNode = new DupNode(object,node,node.next);
             node.next.prior = newNode;
             node.next = newNode;
        }
    

    到了这里,我们就可以初步测试下,双向链表的插入是否有效了,下面创建一个测试类测试下,如下图所示,将几个元素插入到了双向循环链表中,然后输出结果正常,说明插入方法实现无误。有了这些头插法、尾插法直接根据下标即可轻松实现,这里不展示了。
    在这里插入图片描述

    5.提供根据下标删除的方法:remove(int i)

    实现思路其实和单链表的删除是没有区别的:寻找到下标为i-1的数据元素,然后将他的后继更改为i+1的数据元素,然后将下标为i+1的数据元素的前驱更改为,下标为i-1的数据元素即可,实现如下:

        //删除
        public void remove(int i) throws Exception{
            if(i<0 || i>length()-1)
                throw new Exception("下标不合法");
            DupNode node = head;
            int j = 0;
            while(!node.next.equals(head) && j<i){
                j++;
                node = node.next;
            }
            node.next = node.next.next;
            node.next.prior = node;
        }
    

    然后来测试下删除方法,就测试下删除下标为2的元素吧,理论上删除后,输出的应该是:张三、李四、赵柳,如下图可见,输出无误,可见删除方法实现时无误的。
    在这里插入图片描述

    6.提供根据下标获取方法(get(int i))、根据指定结点获取前一个结点方法(getPrior)、根据指定结点获取后一个结点信息方法(getNext)

    上面也说过,双向链表解决的问题就是在获取指定结点的上一个结点时是无需遍历链表的,这样大大节省了时间成本,这里就测试下该方法的实现。三个方法的实现如下所示:

        //根据下标获取
        public Object get(int i) throws Exception{
            return getNode(i).object;
        }
    
        //根据下标获取其前一个元素
        public Object getPrior(int i) throws Exception{
            return getNode(i).prior.object;
        }
    
        //根据下标获取其后一个元素
        public Object getNext(int i) throws Exception{
            return getNode(i).next.object;
        }
    
        public DupNode getNode(int i) throws Exception{
            if(i<0 || i>length())
                throw new Exception("下标不合法");
            DupNode node = head.next;
            int j =0;
            while(node.equals(head) && j<i){
                j++;
                node = node.next;
            }
            return node;
        }
    

    下面我们来测试下这三个方法是否正确,使用李四所在结点来进行测试,李四的下标应该是1,传入1分别运行三个方法,若是正确应该输出的是:李四、张三、王五,如下图可见结果正确。
    在这里插入图片描述

    三、总结

    1.链表的缺点

    线性表的两种实现顺序表、链表。相比于顺序表,链表的缺点就是查找元素比较慢,查找元素的时间复杂度是O(n),而顺序表的时间复杂度是O(1),在查找上顺序表要优于链表,链表查找慢,就是它的缺点了,但是双向循环链表在一定程度上减少了查找时的时间复杂度,但是依然是不及顺序表的查找效率的,所以具体的使用场景还是静态数据适合使用顺序表,动态数据适合使用链表。

    2.链表的优点

    顺序表在指定下标x位置插入元素,组需要后移n-x+1个元素,若是删除下标为x的数据元素,则需要向前移动n-x个数据元素,但是链表则不需要移动任何元素,链表需要做的只是找到对应的元素将指针域中的指针进行更新即可。链表的插入和删除的时间复杂度可以看成O(1),而顺序表插入和删除操作的都是O(n).所以链表的优点就是插入和删除比较快。

    3.如何使用链表

    所以综合链表的优点与缺点我们可以发现,顺序表更适合存储“静态”型数据,而链表更适合存储“动态”型数据,何为动态型呢,就是那些插入、删除操作比较频繁的数据。这类数据更适合使用链表存储。而数据结构不易改变的数据使用顺序表存储更合适。顺序表可类比java中的ArrayList,链表可类比java中的LinkedList。这两者都是顺序表与链表的典型应用。

    展开全文
  • 双向循环链表C++实现

    2016-12-11 10:13:18
    数据结构课程设计实现双向循环链表,我这有详细的课程设计说明书以及答辩ppt,有需要的可以留言哈 ,仅供参考 嘿嘿
  • 循环双链表

    2018-09-12 19:40:16
    基于C高级的双向循环链表,可以选择头插还是尾插两种插入方式,及增加节点,删除节点
  • 静态链表、循环链表双向链表 单链表请点击这里 1.静态链表 C语言具有指针这一强大的功能,也是众多计算机领域的人用来描述数据结构首选C语言的原因之一。指针可以使C非常容易的操作内存中的地址数据,这比其他...

    循环链表、双向链表、静态链表

    三遍定律:
    理解了单链表本文的理解易如反掌,单链表请点击这里
    理解了单链表本文的理解易如反掌,单链表请点击这里
    理解了单链表本文的理解易如反掌,单链表请点击这里

    1.循环链表

    将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表(circular linked list)。

    循环链表可以从任意一个结点出发,访问到链表的全部结点。

    为了使空链表与非空链表处理一致,我们通常设一个头结点,当然,这并不是说,循环链表一定要头结点,这需要注意。循环链表带有头结点的空链表如图:

    在这里插入图片描述

    对于非空的循环链表就如图:

    在这里插入图片描述

    循环链表和单链表的主要差异就在于循环的判断条件上,原来是判断 p->next 是否为空,现在则是
    p->next 不等于头结点,则循环未结束。

    在单链表中,我们有了头结点时,我们可以用O(1)的时间访问第一个结点,但对于要访问到最后一个结点,却需要O(n)时间,因为我们需要将单链表全部扫描一遍。

    有没有可能用O(1)的时间由链表指针访问到最后一个结点呢?当然可以。

    不过我们需要改造一下这个循环链表,不用头指针,而是用指向终端结点的尾指针来表示循环链表,此时查找开始结点和终端结点都很方便了。如图:

    在这里插入图片描述
    从上图中可以看到,终端结点用尾指针 rear 指示,则查找终端结点是 O(1),而开始结点,其实就是rear->next->next,其时间复杂也为 O(1)。

    举个程序的例子,要将两个循环链表合并成一个表时,有了尾指针就非常简单了。比如下面的这两个循环链表,它们的尾指针分别是rearA和rearB,如图:

    在这里插入图片描述
    要想把它们合并,只需要如下的操作即可,如图:

    在这里插入图片描述
    光说不写假把式,代码如下:

    /* 保存 A 表的头结点,即 ① */
    p = rearA->next;
    /* 将本是指向 B 表的第一个结点(不是头结点)赋值给 RearA->next,即 ② */
    rearA->next = rearB->next->next;
    q = rearB->next;
    /* 将原 A 表的头结点赋值给 rearB->next,即 ③ */
    rearB->next = p;
    /* 释放 q */
    free(q);
    

    2.双向链表

    我们的单链表,总是从头到尾找结点,难道就不可以正反遍历都可以吗?当然可以,只不过需要加点东西而已。

    我们在单链表中,有了next指针,这就使得我们要查找下一结点的时间复杂度为O(1)。可是如果我们要查找的是上一结点的话,那最坏的时间复杂度就是O(n)了,因为我们每次都要从头开始遍历查找。

    为了克服单向性这一缺点,我们的老科学家们,设计出了双向链表。双向链表(double linkedlist)是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。所以在双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前驱。

    /* 线性表的双向链表存储结构 */
    typedef struct DulNode{    
    	ElemType data;    
    	struct DuLNode *prior;    /* 直接前驱指针 */    
    	struct DuLNode *next;     /* 直接后继指针 */
    } DulNode, *DuLinkList;
    

    既然单链表也可以有循环链表,那么双向链表当然也可以是循环表。双向链表的循环带头结点的空链表如图:

    在这里插入图片描述
    非空的循环的带头结点的双向链表如图:

    在这里插入图片描述
    由于这是双向链表,那么对于链表中的某一个结点p,它的 后继的前驱 和 它的 前驱的后继 自然也是它自己,即:

    p->next->prior = p = p->prior->next

    双向链表是单链表中扩展出来的结构,所以它的很多操作是和单链表相同的,比如求长度的ListLength,查找元素的 GetElem,获得元素位置的 LocateElem 等,这些操作都只要涉及一个方向的指针即可,另一指针多了也不能提供什么帮助。

    就像人生一样,想享乐就得先努力,欲收获就得付代价。双向链表既然是比单链表多了如可以反向遍历查找等数据结构,那么也就需要付出一些小的代价:在插入和删除时,需要更改两个指针变量。

    插入操作时,其实并不复杂,不过顺序很重要,千万不能写反了。

    我们现在假设存储元素e的结点为s,要实现将结点s插入到结点p和p->next之间需要下面几步,如图:

    在这里插入图片描述
    步骤如下:

    /* 把 p 赋值给 s 的前驱,如图中 ① */
    s->prior = p;
    /* 把p->next赋值给s的后继,如图中 ② */
    s->next = p->next; 
    /* 把s赋值给p->next的前驱,如图中③ */
    p->next->prior = s; 
    /* 把s赋值给p的后继,如图中④ */
    p->next = s;
    

    关键在于它们的顺序,由于第2步和第3步都用到了p->next。如果第4步先执行,则会使得p->next提前变成了s,使得插入的工作完不成。所以我们不妨把上面这张图在理解的基础上记忆,顺序是先搞定s的前驱和后继,再搞定后结点的前驱,最后解决前结点的后继。

    如果插入操作理解了,那么删除操作,就比较简单了。

    若要删除结点p,只需要下面两步骤,如图:

    在这里插入图片描述
    步骤如下:

    /* 把p->next赋值给p->prior的后继,如图中① */
    p->prior->next = p->next;
    /* 把p->prior赋值给p->next的前驱,如图中② */
    p->next->prior = p->prior;
    /* 释放结点 */
    free(p); 
    

    双向链表相对于单链表来说,要更复杂一些,毕竟它多了prior指针,对于插入和删除时,需要格外小心。另外它由于每个结点都需要记录两份指针,所以在空间上是要占用略多一些的。不过,由于它良好的对称性,使得对某个结点的前后结点的操作,带来了方便,可以有效提高算法的时间性能。说白了,就是用空间来换时间。

    3.静态链表

    C语言具有指针这一强大的功能,也是众多计算机领域的人用来描述数据结构首选C语言的原因之一。指针可以使C非常容易的操作内存中的地址和数据,这比其他高级语言更加灵活方便。Java、C#等面向对象语言,虽然不使用指针,但因为启用了对象引用机制,从某种角度也间接实现了指针的某些作用。但对于其他一些语言,如Basic、Fortran等早期的编程语言对于一些数据结构的操作就没有那么方便了。

    有前辈想出来用数组来代替指针,描述单链表:首先让数组的元素都是由两个数据域组成,data和cur。也就是说,数组的每个下标都对应一个data和一个cur。数据域data,用来存放数据元素,也就是通常我们要处理的数据;而cur相当于单链表中的next指针,存放该元素的后继在数组中的下标,我们把cur叫做游标。

    我们把这种用数组描述的链表叫做静态链表,这种描述方法还有起名叫做游标实现法。

    为了我们方便插入数据,通常会把数组建立得大一些,以便有一些空闲空间可以便于插入时不至于溢出。

    用代码描述一下:

    /* 线性表的静态链表存储结构 */
    /* 假设链表的最大长度是1000 */
    #define MAXSIZE 1000
    typedef struct
    {    
    	ElemType data;    
    	/* 游标(Cursor),为0时表示无指向 */    
    	int cur;                             
    } Component,   
    StaticLinkList[MAXSIZE]; 
    

    对数组第一个和最后一个元素作为特殊元素处理,不存数据。我们通常把未被使用的数组元素称为备用链表。而数组第一个元素,即下标为0的元素的cur就存放备用链表的第一个结点的下标;而数组的最后一个元素的cur则存放第一个有数值的元素的下标,相当于单链表中的头结点作用,当整个链表为空时,则为0。如图:

    在这里插入图片描述
    假设我们已经将数据存入静态链表,比如分别存放着“甲”、“乙”、“丁”、“戊”、“己”、“庚”等数据,则它的状态如图所示:

    在这里插入图片描述
    此时“甲”这里就存有下一元素“乙”的游标2,“乙”则存有下一元素“丁”的下标3。而“庚”是最后一个有值元素,所以它的cur设置为0。而最后一个元素的cur则因“甲”是第一有值元素而存有它的下标为1。而第一个元素则因空闲空间的第一个元素下标为7,所以它的cur存有7。

    (1)静态链表的插入操作

    静态链表中解决的是:如何用静态模拟动态链表结构的存储空间的分配,需要时申请,无用时释放。

    在动态链表中,结点的申请和释放分别借用 malloc() 和 free() 两个函数实现。在静态链表中,操作的是数组,不存在像动态链表的结点申请和释放问题,所以我们需要自己实现这两个函数,才可以做插入和删除操作。

    为了辨明数组中哪些分量未被使用,解决办法是将所有未被使用过的及被删除的分量用游标链成一个备用的链表,每当进行插入时,便可以从备用链表上取的第一个结点作为待插入的新结点。

    /* 若备用空间链表非空,则返回分配的结点下标,否则返回 0 */
    int Malloc_SLL(StaticLinkList space)
    {
    	/* 当前数组第一个元素的 cur 存的值,就是要返回的第一个备用的空闲的下标 */
    	int i = space[0].cur;
    	/* 由于要拿出一个分量来使用了,所以我们就得把它的下一个分量用来做备用 */
    	if (space[0].cur)
    		space[0].cur = space[i].cur;
    	return i;
    }
    

    这段代码,一方面它的作用就是返回一个下标值,这个值就是数组头元素的cur存的第一个空闲的下标。从上面的图示例子来看,其实就是返回7。

    那么既然下标为7的分量准备要使用了,就得有接替者,所以就把分量7的cur值赋值给头元素,也就是把8给space[0].cur,之后就可以继续分配新的空闲分量,实现类似malloc()函数的作用。

    现在我们如果需要在“乙”和“丁”之间,插入一个值为“丙”的元素,按照以前顺序存储结构的做法,应该要把“丁”、“戊”、“己”、“庚”这些元素都往后移一位。但目前不需要,因为我们有了新的手段。

    新元素“丙”,想插队是吧?可以,你先悄悄地在队伍最后一排第7个游标位置待着,我一会就能帮你搞定。我接着找到了“乙”,告诉他,你的cur不是游标为3的“丁”了,这点小钱,意思意思,你把你的下一位的游标改为7就可以了。“乙”叹了口气,收了钱把cur值改了。此时再回到“丙”那里,说你把你的cur改为3。就这样,在绝大多数人都不知道的情况下,整个排队的次序发生了改变。如图:

    在这里插入图片描述
    上图实现代码如下:

    /* 在 L 中第 i 个元素之前插入新的数据元素 e */
    Status ListInsert(StaticLinkList L, int i, ElemType e)
    {
    	int j, k, l;
    	/* 注意 k 首先是最后一个元素的下标 */
    	k = MAX_SIZE - 1if (i < 1 || i > ListLength(L) + 1)
    		return ERROR;
    	/* 获得空闲分量的下标 */
    	j = Malloc_SLL(L);
    	if (j)
    	{
    		/* 将数据赋值给此分量的data */         
    		L[j].data = e;                           
    		/* 找到第i个元素之前的位置 */         
    		for (l = 1; l <= i - 1; l++)                 
    			k = L[k].cur;         
    		/* 把第i个元素之前的cur赋值给新元素的cur */         
    		L[j].cur = L[k].cur;                 
    		/* 把新元素的下标赋值给第i个元素之前元素的cur */         
    		L[k].cur = j;       
    		return OK;
    	}
    	return ERROR;
    } 
    

    当我们执行插入语句时,我们的目的是要在“乙”和“丁”之间插入“丙”。调用代码时,输入 i 值为 3。
    让 k = MAX_SIZE-1=999。
    j = Malloc_SSL(L) = 7。此时下标为 0 的 cur 也因为 7 要被占用而更改备用链表的值为 8。
    for 循环 l 由 1 到 2,执行两次。代码 k = L[k].cur ; 使得 k = 999,得到 k = L[999].cur = 1,再得到 k = L[1].cur = 2。
    L[j].cur = L[k].cur ; 因 j = 7,而 k = 2 得到L[7].cur=L[2].cur = 3。这就是刚才我说的让“丙”把它的cur 改为 3的意思。
    L[k].cur = j;意思就是L[2].cur = 7。也就是让“乙”得点好处,把它的 cur 改为指向“丙”的下标 7。
    就这样,我们实现了在数组中,实现不移动元素,插入了数据的操作。

    (2)静态链表的删除操作

    删除元素时,原来是需要释放结点的函数 free()。现在我们要自己实现它:

    /* 删除在 L 中第 i 个元素 e */
    Status ListDelete(StaticLinkList L, int i)
    {
    	int j, k;
    	if (i < 1 || i > ListLength(L))
    		return ERROR;
    	k = MAX_SIZE - 1;
    	for (j = 1; j <= i; j++)
    		k = L[k].cur;
    	j = L[k].cur;
    	L[k].cur = L[i].cur;
    	Free_SSL(L,j);  /* 看下文 */
    	return OK;
    }
    

    将下标为k的空闲结点回收到备用链表代码:

    /* 将下标为k的空闲结点回收到备用链表 */
    void Free_SSL(StaticLinkList space, int k)
    {
    	/* 把第一个元素 cur 值赋给要删除的分量 cur */
    	space[k].cur = space[0].cur;
    	/* 把要删除的分量下标赋值给第一个元素的 cur */
    	space[0].cur = k;
    }
    

    甲现在要走,这个位置就空出来了,也就是,未来如果有新人来,最优先考虑这里,所以原来的第一个空位分量,即下标是 8 的分量,它降级了,把 8 给“甲”所在下标为 1 的分量的 cur,也就是 space[1].cur = space[0].cur = 8,而 space[0].cur = k = 1 其实就是让这个删除的位置成为第一个优先空位,把它存入第一个元素的 cur 中,如图:

    在这里插入图片描述
    (3)静态链表的优缺点

    上图:

    在这里插入图片描述

    展开全文
  • 单向循环链表: ...双向循环链表直接体现为 双向循环,一般的单链表只有节点数据datanext指向地址(应该也是引用的意思),而在此需要增加前面部分的pre指向地址,同时还需要循环循环则在定义节点时可以解决,
  • 提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 ...循环链表就是解决这个麻烦的重要方法。 一、循环链表的定义 1、定义 将单链表中终端结点的指针端由空指针改为指向头指针,就使整个单链表形..
  • 循环链表和双向链表

    2019-08-09 16:48:15
    循环链表及其操作: 就是将尾指针指向头部: 如:#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<malloc.h> typedef struct data { int num; struct data *next...
  • 主要介绍了C++的循环链表双向链表设计的API实现,文中的示例对于链表结点的操作起到了很好的说明作用,需要的朋友可以参考下
  • 从linux内核移植出来,独立于用户数据结构,里面有实例参考。C语言代码。
  • Josephus问题可以描述为如下的一个游戏:N个人编号从1到N,围坐成一个圆圈,从1号开始传递一个热土豆,经过M次传递后拿着土豆的人离开圈子,由坐在离开的人的后面的人拿起热土豆继续进行游戏,直到圈子只剩下最后一...
  • 第二,大家在很多书上看到的是使用单链表实现队列,我这里将会使用带头结点尾结点的非循环双链表实现,虽然多维护了两个节点指针域,但是在链表头尾进行插入删除的时候不需要遍历链表了,队列操作变得非常
  • 本文实例讲述了Python双向循环链表实现方法。分享给大家供大家参考,具体如下: 最近身边的朋友在研究用python来实现数据结构。遇到一个问题就是双向循环链表的实现,改指向的时候总是发蒙。 我自己尝实现了一个...
  • 链表不仅作为链式存储的一种实现方式,还表达了计算机不连续(离散)的存储思想。
  • 单链表、循环链表、双向循环链表总结

    千次阅读 多人点赞 2019-11-23 18:56:31
    链表介绍 不带头结点的单向链表 带头结点的单向链表 循环链表 双向循环链表
  • C语言版双向循环链表双向循环链表经典程序,用于指针进行编写的C语言程序。。。
  • 用C++Java实现带头节点的双向循环链表,要继承linearList类,并实现它的所有功能,另外,必须实现双向迭代器。 实现带头节点的双向循环链表,要具有以下的功能: 判断表是否为空,如果为空则返回true,不空返回...
  • 循环链表 循环链表(Circular Linked List)是另一种形式的链式存储结构。其特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。 循环单链表的操作单链表基本一致,差别仅在于:当链表遍历时,...
  • 1、单项循环列表单向循环链表是单链表的另一种形式,其结构特点是链表中最后...2、单向循环链表和单链表相同,循环单链表也有带头结点结构和不带头结点结构两种,带头结点的循环单链表实现插入和删除操作时,算法实...
  • 图展示的是一个单向循环链表,他跟以上的单向链表对比只是多了一个指向头结点的 指针,因此,他们的算法几乎是一样的。 第一,设计节点。 单向循环链表的节点跟单向链表完全一致。 第二,初始化空链表。 跟单向链表...
  • 双向循环链表

    2017-10-13 14:10:31
    双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点后继结点...一般我们都构造双向循环链表
  • 静态链表、循环链表和双向链表 1.静态链表 早期语言如Basic,由于没有指针,链表结构无法实现,就有人想出来用数组代替指针描述单链表。 数组的元素都是两个数据域组成,data和cur,即数组的每个下表都对应一个data...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 109,114
精华内容 43,645
关键字:

循环双链表和双循环链表