首页 > 其他 > 详细

数据结构学习(三)——线性表(二)

时间:2019-10-18 11:39:13      阅读:50      评论:0      收藏:0      [点我收藏+]

单链表

  单链表指的是线性表的每个结点,分散地存储在内存空间中,向后依次用一个指针串联起来。

单链表的一般形式

不带表头结点

  技术分享图片

 

 

 

   

 

  其中:data称为数据域,next称为指针域/链域;当head==NULL时,为空表;否则为非空表,表为一个非空表时,在首结点,*head中会存放数据。

带表头结点

非空表

技术分享图片

 

 

 

空表

技术分享图片

  其中:head指向表头结点,head->data不放元素,head->next指向首结点a1,当head->next==NULL时,为空表。

单链表的结点结构

1 1 struct node
2 2 {
3 3     ElemType data;    //data为抽象元素类型
4 4     struct node *next;    //next为指针类型
5 5 };
6 //指向结点的指针变量head、p、q说明
7 struct node *head, *p, *q;

生成单链表

  例1:输入一列整数,以0为结束标志,生成“先进先出”单链表“

技术分享图片

  这个单链表之所以称为先进先出单链表就是因为先进来的元素;通过变化表头指针可以先被删除。

  首先定义结点空间所占大小,结点的数据域为整型数,然后定义指针域next。

1 #define LENG sizeof(struct node)    //结点所占单元数
2 struct node    //定义结点类型
3 {
4     int data;
5     struct node *next;
6 };

  每次输入元素后:

  1. 生产新结点:p=malloc(结点大小); p->data=e; p->next=NULL;
  2. 添加到表尾:tail->next=p;
  3. 设置新表尾:tail=p;

生成”先进先出“单链表(链式队列)

 1 struct node *creat1()
 2 {
 3     struct node *head, *tail, *p;
 4     int e;
 5     head=(struct node *)malloc(LENG);    //生成表头结点
 6     tail=head;    //尾指针指向表头
 7     scanf("%d", &e);    //输入第一个数
 8     while(e!=0);    //不为0
 9     {
10         p=(struct node *)malloc(LENG);    //生成新结点
11         p->data=e;    //装入输入的元素e
12         tail->next=p;    //新结点链接到表尾
13         tail=p;    //尾指针指向新结点
14         scanf("%d", &e);    //再输入一个数
15     }
16     tail->next=NULL;    //尾结点的next置为空指针
17     return head;    //返回头指针
18 }

生成”先进后出“单链表(链式栈)

技术分享图片

 

 

   每次插入新元素后:

  1. 生成新结点:p=malloc(结点大小); p->data=e;
  2. 新结点指针指向原首结点:p->next=head->next;
  3. 新结点作为首元素:head->next=p;
 1 struct node *creat2()
 2 {
 3     struct node *head, *p;
 4     head=(struct node *)malloc(LENG);    //生成表头结点
 5     head->next=NULL;    //置为空表
 6     scanf("%d", &e);    //输入第一个数
 7     while(e!=0);    //不为0
 8     {
 9         p=(struct node *)malloc(LENG);    //生成新结点
10         p->data=e;    //输入数送新结点的data
11         p->next=head->next;    //新结点指向原首结点
12         head->next=p;    //表头结点的指针指向新结点
13         scanf("%d", &e);    //再输入一个数
14     }
15     return data;    //返回头指针
16 }

插入一个结点

  例1:在已知 p 指针指向的结点后插入一个元素x

  首先用一个指针 f 指向新结点,该结点中的数据域为 x ,然后此新结点next域赋值为 p 指针指向结点的next域,最后 p 指针指向结点的next域赋值为 f 。

1 f=(struct node *)malloc(LENG);    //生成
2 f->data=x;    //装入元素x
3 f->next=p->next;    //新结点指向p的后继
4 f->next=f;    //新结点成为p的后继

  例2:在已知 p 指针指向的结点前插入以一个元素x

  因为单链表每个结点只有一个指针指向其后继结点,如果在结点前插入一个新结点,就需要得到指向 p 结点前驱结点的指针。

1 f=(struct node *)malloc(LENG);
2 f->data=x;
3 f->next=p;    //新结点成为p的前驱
4 q->next=f;    //新结点成为p的前驱结束的后继

单链表的算法

表头结点的作用

  例:输入一列整数,以0为结束标志,生成递增有序单链表。

技术分享图片

技术分享图片

 

 

   可以分为以下几种情况:

  1. p、q同时空,意味着往空表中插入第一个结点;
  2. 仅 p 为空,q不为空,尾部插入,即数据插入到链表的尾部;
  3. (仅q为空)首部插入,即插入的数据作为单链表的第一个结点。

  p、q可能为NULL

  1. (p、q同时空)空表插入;f->next=p;
  2. (仅p为空)尾部插入;q->next=f;
  3. (仅q为空)首部插入;q不可能为空。

  注意一点每次扫描已经存在的单链表确定数据插入的位置之前,做如下的初始化:

1 q=NULL;
2 p=Head;

递增有序单链表的算法

 1 struct node *creat3_1(struct node *head, int e)
 2 {
 3     q=NULL;
 4     p=head;    //p、q扫描,查找插入位置
 5     while(p&&e>p->data)    //未扫描完,且e大于当前结点
 6     {
 7         q=p;
 8         p=p->next; 
 9     }
10     f=(struct node *)malloc(LENG);    //生成新结点
11     f->data=e;    装入元素e
12     if(p==NULL)
13     {
14         f->next=NULL;
15         if(q==NULL)    //对空表的插入
16             head=f;
17         else
18             q->next=f;
19     }    //作为最后一个结点插入
20     else if(q==NULL)    //作为第一个结点插入
21     {
22         f->next=p;
23         q->next=f;    //一般情况插入新结点
24     }
25     return head;
26 }

主函数算法

 1 main()
 2 {
 3     struct node *head;    //定义头指针
 4     head=NULL;    //置为空表
 5     scanf("%d", &e);    //输入整数
 6     while(e!=0)    //不为0,未结束
 7     {
 8         head=creat3_1(head, e)    //插入递增有序单链表
 9         scanf("%d", &e);
10     }
11 }

生成带头结点的递增有序单链表(不包括0)

 1 void creat3_2(struct node *head, int e)
 2 {
 3     q=head;
 4     p=head->next;    //p、q扫描,查找插入位置
 5     while(p&&e->p->data)    //未扫描完,且e大于当前结点
 6     {
 7         q=p;
 8         p=p->next;    //q、p后移,查下一个位置
 9     }
10     f=(struct node *)malloc(LENG);    //生成新结点
11     f->data=e;    //装入新元素e
12     f->next=p;
13     q->next=f;    //插入新结点
14 }

主函数算法

main()
{
    struct node *head;    //定义头指针
    head=NULL;    //置为空表
    scanf("%d", &e);    //输入整数
    while(e!=0)    //不为0,未结束
    {
        head=creat3_2(head, e)    //插入递增有序单链表
        scanf("%d", &e);
    }
}    

单链表的指定位置插入新元素

  输入:头指针L、位置i、数据元素e

  输出:成功返回OK,失败返回ERROR

  计数如果在第 i 个位置上结束,p 指向第 i 个位置,新元素就要插入到 p 指向的结点之前,我们之前分析过,如果插入到某个结点之前,就需要另一个复制指针 q 来指向 p 的前驱结点。

技术分享图片

  执行:p=L

  当 p 不为空,执行 p=p->next  i-1次

  定位到第 i-1 个结点

  当 i<1 或 p 为空时插入点错

  否则新结点加到 p 指向结点之后

 1 int insert(Linklist &L, int i, ElemType e)
 2 {
 3     p=L;
 4     j=1;
 5     while(p&&j<i)
 6     {
 7         p=p->next;    //p后移,指向下一个位置
 8         j++;
 9     }
10     if(i<1||p==NULL)    //插入点错误
11         return ERROR;
12     f=(Linklist)malloc(LENG);    //生成新结点
13     f->data=e;    //装入元素e
14     f->next=p->next;
15     p->next=f;    //插入新结点
16     return OK;
17 }

   主要是三个大的部分:

  1. 扫描定位;
  2. 判断插入点位置为合法性;
  3. 在 p 指定的位置后面插入到新的结点。

在单链表中删除一个结点

技术分享图片

    1. 执行:q->next=p->next;  //A的next域=C的地址(B的next)
    2. 执行:free(p);  //释放 p 所指向的结点空间

算法1:在带表头结点的单链表中删除元素值为e的结点

 1 int Delete1(Linklist head, ElemType e)
 2 {
 3     struct node *q, *p;
 4     q=head;
 5     p=head->next;    //q、p扫描
 6     while(p&&p->data!=e)    //查找元素为e的结点
 7     {
 8         q=p;    //记住前一个结点
 9         p=p->next;    查找下一个结点
10     }
11     if(p)    //有元素为e的结点
12     {
13         q->next=p->next;    //删除该结点
14         free(p);    //释放结点所占的空间
15         return YES;
16     }
17     else
18         return NO;    //没有删除结点
19 }

算法2:在单链表中删除指定位置的元素

技术分享图片

 

  执行:p=L;

  当 p 不为空,执行p=p->next i-1次,定位到第 i-1 个结点

  当 i<1 时或p->next为空时删除点错,否则 p 指向后继结点的后继跳过原后继

 1 int Delete2(Linklist &L, int i, ElemType &e)
 2 {
 3     p=L;
 4     j=1;
 5     while(p->next&&j<i)    //循环结束时p不可能为空
 6     {
 7         p=p->next;    //p后移,指向下一个位置
 8         j++;
 9     }
10     if(i<1||p->next==NULL)    //删除点错误
11         return ERROR;
12     q=p->next;    //q指向删除结点
13     p->next=q->next;    //从链表中指出
14     e=q->data;    //取走数据元素值
15     free(p);    //释放结点空间
16     return OK;
17 }

单链表的合并

两个有序单链表的合并算法

  将两个有序单链表La和Lb合并为有序单链表Lc;(该算法利用原单链表的结点)

技术分享图片

  输入:两单链表的头指针

  输出:合并后的单链表的头指针

 1 struct node *merge(struct node *La, struct node *Lb)
 2 {
 3     struct node *pa, *pb, *pc;
 4     pa=La->next;    //pa指向表La的首结点
 5     pb=Lb->next;    //pb指向表Lb的首结点
 6     pc=La;    //使用表La的头结点,pc为尾指针
 7     free(Lb);    //释放表Lb的头结点
 8 //比较pa指向pb指向结点的数据域值大小
 9     while(pa&&pb)    //表La表Lb均有结点
10     if(pa->data<=pb->data)    //取表La的一个结点
11     {
12         pc->next=pa;    //插在表Lc的尾结点之后
13         pc=pa;    //变为表Lc新的尾结点
14         pa=pa->next;    //移向表La下一个结点
15     }
16     else    //取表Lb的一个结点
17     {
18         pc->next=pb;    //插在表Lc的尾结点之后
19         pc=pb;
20         pb=pb->next;
21     }
22     if(pa)
23         pc-next=pa;    //插入表La的剩余段
24     else
25         pc->next=pb;    //插入表Lb的剩余段
26     return La;
27 }

循环链表

一般形式

带表头结点的非空循环单链表

技术分享图片

 

 

   有:H->next≠H,H≠NULL。

带表头结点的空循环单链表

技术分享图片

 

 

   有:H->next≠H,H≠NULL。

只设尾指针的循环链表

非空表

技术分享图片

 

 

   有:tail指向表尾结点

  tail->data=an

  tail->next 指向表头结点

  tail->next->next 指向首结点

  tail->next->next->data=a1

空表

技术分享图片

 

   有:tail->next=tail

例:两循环链表首尾相连

  如果是带头指针,时间复杂度 O(m+n)

  如果使用只设尾指针的循环链表

1 p2=tail2->next;
2 tail->next=tail->next;
3 tail1->next=p2->next;
4 free(p2);

  时间复杂度 O(1)

循环链表算法举例

  例:求以head为头指针的循环单链表的长度,并依次输出结点的值。

 1 int length(struct node *head)
 2 {
 3     int leng=0;    //长度变量初值为0
 4     struct node *p;    
 5     p=head->next;    //p指向首结点
 6     while(p!=head)    //p未移回到表头结点
 7     {
 8         printf("%d", p->data);    //输出
 9         leng++;    //计数
10         p=p->next;    //p移向下一个结点
11     }
12     return leng;
13 }

双向链表

技术分享图片

 1 //结点类型定义
 2 struct Dnode
 3 {
 4     ElemType data;    //data为抽象元素类型
 5     struct Dnode *prior, *next;    //prior,next为指针类型
 6 };
 7 //或者
 8 typedef struct Dnode
 9 {
10     ElemType data; 
11     struct Dnode *prior, *next;
12 } *DList

一般形式

非空表

技术分享图片

 

  有:L为头指针,L指向表头结点,L->next指向首结点

  L->next->data=a1

  L->prior指向尾结点,L->prior->data=an

  L->next->prior=L->prior->next=NULL

空表

技术分享图片

 

   有:L->next=L->prior=NULL

双向循环链表

空表

技术分享图片

 

   有:L->next=L->prior=L

非空表

技术分享图片

 

   设 p 指向 a1,有:

  p->next 指向 a2 ,p->next->prior 指向 a1

  所以,p=p->next->prior

  同理,p=p->prior->next

已知指针p指向结点B,删除B

 技术分享图片

1 //执行
2 p->prior->next=p->next;    //结点A的next指向结点C
3 p->next->prior=p->prior;    //结点C的prior指向结点A
4 free(p);    //释放结点B占有的空间

已知指针p指向结点C,在A,C之间插入B

1 f->prior=p->prior;    //结点B的prior指向结点A
2 f->next=p;    //结点B的next指向结点C
3 p->prior->next=f;    //结点A的next指向结点B
4 p->prior=f;    //结点C的prior指向结点B

 

数据结构学习(三)——线性表(二)

原文:https://www.cnblogs.com/tronysh/p/11695484.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!