slide1
Download
Skip this Video
Download Presentation
第十二讲 链表插入结点

Loading in 2 Seconds...

play fullscreen
1 / 45

第十二讲 链表插入结点 - PowerPoint PPT Presentation


  • 67 Views
  • Uploaded on

第十二讲 链表插入结点. 链表插入结点. 原则: 1 、插入操作不应破坏原链接关系 2 、插入的结点应该在它该在的位置。应该有一个插入位置的查找子过程。. 先看下面一个简单的例子: 已有一个如图所示的链表。它是按结点中的整数域从小到大排序的。现在要插入一个结点,该节点中的数为 10 。. head. 此结点已插入链表. 待插入结点. 参考程序. // 结构 7.c #include <stdio.h> // 预编译命令 #include <malloc.h> // 内存空间分配 #define null 0 // 定义空指针常量

loader
I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.
capcha
Download Presentation

PowerPoint Slideshow about ' 第十二讲 链表插入结点' - quintessa-haley


An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript
slide2
链表插入结点
  • 原则:
  • 1、插入操作不应破坏原链接关系
  • 2、插入的结点应该在它该在的位置。应该有一个插入位置的查找子过程。
slide4
参考程序

// 结构7.c

#include // 预编译命令

#include // 内存空间分配

#define null 0 // 定义空指针常量

#define LEN sizeof(struct numST) // 定义常量,表示结构长度

struct numST // 结构声明

{

int num; // 整型数

struct numST *next; // numST结构指针

};

slide5
// 被调用函数insert(),两个形参分别表示链表和待插入的结点

void insert (struct numST **phead, struct numST *p)

{ // 函数体开始

struct numST *q,*r; // 定义结构指针q,r

if ((*phead)==null) // 第一种情况,链表为空

{

*phead = p; // 链表头指向p

return; // 完成插入操作,返回

}

else // 链表不为空

{

// 第二种情况,p结点num值小于链表头结点的num值

if ((*phead)->num > p->num )

{// 将p结点插到链表头部

p->next = *phead;// 将p的next指针指向链表头(*phead)

*phead = p;// 将链表头赋值为p

return; // 返回

}

slide6
// 第三种情况,循环查找正确位置

r = *phead; // r赋值为链表头

q = (*phead)->next; // q赋值为链表的下一个结点

while (q!=null) // 利用循环查找正确位置

{

// 判断当前结点num是否小于p结点的num

if (q->num < p->num)

{

r = q; // r赋值为q,即指向q所指的结点

q = q->next;// q指向链表中相邻的下一个结点

}

else // 找到了正确的位置

break; // 退出循环

}

// 将p结点插入正确的位置

r->next = p;

p->next = q;

}

}

slide7
// 被调用函数,形参为ST结构指针,用于输出链表内容

void print(struct numST *head)

{

int k=0; // 整型变量,用于计数

struct numST * r; // 声明r为ST结构指针

r=head; // r赋值为head,即指向链表头

while(r != null) // 当型循环,链表指针不为空则继续

{ // 循环体开始

k=k+1; // 计数加1

printf("%d %d\n",k,r->num);

r=r->next; // 取链表中相邻的下一个结点

} // 循环体结束

}

slide8
void main() // 主函数开始

{ // 函数体开始

struct numST *head, *p;// ST型结构指针

head = null; // 初始化head为null

// 分配3个ST结构的内存空间,用于构造链表

head = (struct numST *) malloc(LEN);

head->next = (struct numST *) malloc(LEN);

head->next->next = (struct numST *) malloc(LEN);

// 为链表中的3个结点中的num赋值为5、10和15

head->num = 5;

head->next->num = 10;

head->next->next->num = 15;

head->next->next->next = null; // 链表尾赋值为空

// 构造一个结点p,用于插入链表

p = (struct numST *) malloc(LEN);

p->num = 12;

p->next = null;

insert(&head, p); // 调用insert函数将结点p插入链表

print(head); // 调用print函数,输出链表内容

} // 主函数结束

slide9
先看主函数

1、定义两个ST型结构指针*head,*p,并让head=null;

2、分配3个ST结构的内存空间,用于构造链表

(1)head=(struct numST*) malloc(LEN);

(2)head->next=(struct numST*) malloc(LEN);

(3)head->next->next=(struct numST*) malloc(LEN);

head

head->next

head->next->next

这3个ST结构的内存空间如上图所示。

slide10
下面用赋值语句往这3个空间中放num数据。最后的一个结点为队尾,在其指针域存放null。

(4)head->num=5;

(5)head->next->num=10;

(6)head->next->next->num=15;

(7)head->next->next->next=null;

做了这4条之后形成了一条链表如下:

head

该链表的头结点由head所指向。

slide11
3、构造一个结点p,在p结点的数据域放12,再插入链表

(1)p=(struct numST*) malloc(LEN);

(2)p->num=12;;

(3)p->next=null;

4、调用insert函数来插入p结点。

语句为 insert(&head,p);

意思是以将p插入到以head为队头的链表中。但这里在调用时,不是用head作为实参,而是用&head作为实参,属于传址调用,而非传值调用。

slide12
这里要讲传址调用和传值调用的区别

(1)如果是传值调用主程序中的调用语句为 insert(head,p);被调用函数为void insert(struct munST *phead, struct numST*p);

实际参数

head

p

p

phead

形式参数

slide13
当着实际参数head赋给了形式参数phead之后,phead就指向了已经存在了的链表,见下图。

head

phead

这时原来的主程序中的头指针就不再起作用了。而是phead起作用。假如现在p中的结点数据为4小于5,应该将p插入到phead所指向的结点前,如下图

slide14
head

phead

phead

被调用函数无法改变head,这时head不再是头结点的指针了。如果被调用函数返回head,主函数只能表示head为头指针的三个结点,新插入的结点并没有包含进去。要想将新的插入到最前面的结点包含进去,就必须用传址调用。

slide15
(2)如果是传址调用主程序中的调用语句为 insert(&head,p);

被调用函数为

void insert(struct munST **phead, struct numST*p);先看struct numST **phead

是说定义(*phead)为指向numST结构的指针。*phead是指向numST结构的指针,phead又是指向*phead这个指针的指针。phead=&head

slide16
&head

head

phead

主程序中的实参为链表头指针head所在的地址值,传给被调用函数的phead的指针变量中,起到了让phead也指向head的目的。

slide17
在主函数中head为头指针,在被调用的子函数中phead为头指针的地址,head和*phead是同一个单元,只不过分别叫不同的名罢了。当然在子函数中无论插入什么结点都会让*phead指向链表的头。自然返回到主函数后,head也会是指向同一链表的头。

从这个例子中读者可以领回到传值调用与传址调用的区别。

5、这样在子函数做插入结点的过程中,头指针的改变也能反映到主函数中来。调用print函数,从head开始输出整个链表的内容。

slide18
下面我们来研究insert函数

前提是主程序已将两个实参传给了insert函数的两个形参,这时*phead=head,p所指向的就是待插入的一个结点。事先定义两个结构指针q和r。

第一种情况:

*phead==null,说明主程序传过来的头指针为空,即链表为空,一个结点都不存在。这时待插入的p结点就是链表中的第一个结点。只要执行如下两条语句即可

*phead=p; // 将表头指针指向p结点

return; // 返回主程序

在主程序中必然头指针head指向p结点。

slide19
第二种情况:

p结点的num值小于链表头结点的num值,即(*phead)->num>p->num。这时要将p结点插入到头结点的前面,要执行如下三条语句

p->next=*phead; // 在p结点的指针域赋以头结点的地址值

*phead=p; // 将头结点指针phead指向p结点

return; // 返回主程序

这种情况如下图

*phead

*phead

null

p

slide20
第三种情况:

前两种情况,无论遇到哪一种,都会返回主程序。只要不返回就是这第三种情况,即p结点的num大于头指针所指向的结点的num值。这时肯定地说p结点要插入到头结点之后,究竟要插到哪里需要找到应该插入的位置。我们设指针r和指针q,分别指向相邻的两个结点,r在前q在后,当着满足

r->num < p->num < q->num

时,p就插在r与q之间。我们看图

r

q

r

q

head

p

null

slide21
一开始让 r=*phead,让 q=*phead->next

(1) 当指针q为空指针时,说明原链表中只有一个结点,即r指向的结点,这时只要将p结点接在r之后即可。执行

r->next=p;

p->next=q;

(2) 如果q!=null,说明起码有两个结点在链表中,接着要判p结点的num值是否大于q结点的num值。如果是大,则说明p应插在q之后而不是之前,这时让r和q指针同时后移一步,即

r=q;

q=q->next;

slide22
执行(2)

在q!=null的情况下,如果p->num<=q->num了,说明这时找到了正确的插入位置,退出while循环,将p结点插入到r后,q前即可。使用的语句为

r->next=p;

p->next=q;

在下面我们画出该算法的结构框图

slide24
作业

1、按下表顺序输入某班的一个学习小组的成员表

希望你将学习小组形成一个链表,每人一个结点。结点中有四个成员:姓名、出生年、出生月、指针。在链表中生日大者在前,小者在后。

建成链表后输出该链表。

slide25
2、一年后钱亮同学调至其它学习小组,希望你编程从原链表中删除钱亮所在结点,之后输出该链表。

提示:原链表如下:

head

查找待删除的结点的位置,要从链头找起。

(1)如果是链头结点,即有head->name==待删者name这时只要做head=head->next;即可

(2)如果不是链头结点,要设两个指针r和q,初始时让r=head; q=head->next;

slide26
(3)只要q!=null,就比较q->name是否为待删者的name?如果是则让 r->next=q->next; 如不是,就让r与q同时后移一步,即 r=q; q=q->next;然后转向(3)

(4)如果发现q已是null,又未找到待删结点,则输出该人不在这个表中的信息。在原链表中一旦查到钱亮所在结点位置q,让

r->next = q->next;

意味着将孙参所在结点指向钱亮的指针,不再指向钱亮,而指向武陆

slide28
链表结构是利用结构中的指针域将每个结点链接起来,形似链条,属于线性结构数据。

下面介绍一种非线性结构的东西,二叉树。

先看下例:

root

结点4

结点6

结点2

结点7

结点3

结点5

结点1

slide29
在图中
  • (1)外形象一棵倒立的树
  • (2)最上层有一个“根结点”,指针root指向根结点。
  • (3)每个结点都是一个结构,一个成员是整型数据,两个成员是指针,分为左指针L和右指针R。
  • (4)根结点的左指针指向左子树;右指针指向右子树。
  • (5)左子树或右子树本身又是一棵二叉树,又有它们自己的左子树和右子树,……

这是递归定义的,因此,在处理时常可用递归算法。

slide30
二叉树的遍历

树的遍历是指访遍树中的所有结点。

对比看遍历一个单链表,从表头开始按一个方向从头到尾就可遍历所有结点。对二叉树来说就没有这样简单了,因为对树或是对子树都存在根(或子树的根)和左子树、右子树,先遍历谁?由之产生了三种不同的方法:

slide31
1、前序法:

1.1 先访问根结点;

1.2 遍历左子树;

1.3 遍历右子树;

2、中序法:

2.1 遍历左子树;

2.2 访问根;

2.3 遍历右子树;

3、后序法

3.1 遍历左子树;

3.2 遍历右子树;

3.3 访问根;

slide32
我们就以中序法为例研究如何遍历二叉树。仍然采用递归算法。令指针p指向二叉树的根结点
  • 定义树的结构
      • struct TREE
      • {
      • int data;
      • struct TREE *L, *R;
      • };
  • 定义p为TREE结构的指针
  • struct TREE *p;
  • 让LNR(P)为对以p为根的树作中序遍历的子函数。可得出如下图所示的递归算法与或结点图
slide34
该图说明如下:
  • 1、A结点表示中序遍历p结点为根的二叉树,函数为LNR(p)。该结点为“或”结点,有两个分支。当p为空时,A取B结点,什么都不做;当p不空时,说明树存在(起码有一个根),有结点C。
  • 2、C结点为一个“与”结点,要依次做相关联的三件事情:
  • 2.1 D结点:中序遍历p的左子树,函数为LNR(p->L);
  • 2.2 E结点:直接可解结点,访问p结点(比如输出p结点数据域中的值)。
  • 2.3 F结点:中序遍历p的右子树,函数为LNR(p->R)
slide35
3、比较LNR(p)与LNR(p->L)及LNR(p->R)可以看出,都是同一个函数形式,只不过代入了不同的参数,从层次和隶属关系看,p是父结点的指针,而p->L和p->R是子结点的指针,p->L是左子树的根,p->R是右子树的根。

下面请大家做一个练习,依图2画“与或”图将图1所示的二叉树用中序遍历,将所访问到的结点数据输出。如图3

slide36
什么都不做

访问结点1:输出3

slide37
二叉树的建立

建立二叉树的过程是一个“插入”过程,下面我们用一个例子来讲解这一过程。

我们想建立这样一棵二叉树,树中的每一个结点有一个整数数据名为data,有两个指针:左指针L,右指针R,分别指向这个结点的左子树和右子树,显然可以用如下名为TREE的结构来描述这种结点:

struct TREE

{

int data;

struct TREE *L, *R;

}

slide38
对二叉树最重要的是根,它起定位的作用,因此,首先建立的是根结点。也就是说,如果从键盘输入数据来建立二叉树,第一个数据就是这棵树的根的数据,之后再输入的数据,每一个都要与根中的数据作比较,以便确定该数据所在接点的插入位置。假定我们这里依然用图1的中序遍历的方式。即如果待插入结点的数据比根结点的数据小,则将其插至左子树,否则插入右子树。

定义一个递归函数

void insert(struct TREE **proot, struct TREE *p)

其中,指针p指向含有待插入数据的结点。

proot为树的根指针的地址。

insert函数棵理解为将p结点插入到*proot所指向的树中。

slide40
注意在上图中proot是被调用函数的形参。从前面对它的定义看,proot是指针的指针,实际上是指向二叉树根结点的指针的指针,或者说是指向二叉树根结点的指针的地址。如下图。因此,在主程序调用insert函数时,

实参

根结点

&root

proot

实参为 &root,p

形参为 proot, p

下面是建立二叉树的参考程序。

slide41
#include // 预编译命令

#include // 内存空间分配

#define null 0 // 定义空指针常量

#define LEN sizeof(struct TREE) // 定义常量,表示结构长度

struct TREE // 结构声明

{

int data; // 整型数

struct TREE *L,*R; // TREE结构指针

};

slide42
// 被调用函数insert,将结点插入二叉树

void insert (struct TREE **proot, struct TREE* p)

{ // 函数体开始

if (*proot==null) // 如果根结点为空

{

*proot = p; // 将结点p插入根结点

return; // 返回

}

else // 根结点不为空

{

// 如果p结点数据小于等于根结点数据

if (p->data <= (*proot)->data)

insert( &((*proot)->L), p); // 插入左子树

else // 如果p结点数据大于等于根结点数据

insert( &((*proot)->R), p); // 插入右子树

}

} // 函数体结束

slide43
// 被调用函数,形参为TREE结构指针,输出二叉树内容

void print(struct TREE *root)

{ // 函数体开始

if (root == null) // 根或子树根结点为空

return; // 返回

print(root->L); // 输出左子树内容

printf("%d",root->data);// 输出根结点内容

print(root->R); // 输出右子树内容

} // 被调用函数结束

void main() // 主函数开始

{ // 函数体开始

struct TREE *root, *p; // TREE型结构指针

int temp; // 临时变量,用于用户输入数据

root = null; // 初始化二叉树根结点为空

p = null; // 初始化待插入结点的指针为空

printf("请输入待插入结点的数据\n"); // 提示信息

printf("如果输入-1表示插入过程结束\n");// 提示信息

scanf("%d",&temp); // 输入待插入结点数据

slide44
while(temp != -1) // 当型循环,-1为结束标志

{ // 循环体开始

// 为待插入结点分配内存单元

p = (struct TREE *) malloc(LEN);

p->data = temp; // 将temp赋值给p结点的数据域

p->L = p->R = null; // 将p结点的左右指针域置为空

insert( &root, p ); // 将p结点插入到根为root的树中, // &root表示二叉树根结点的地址

printf("请输入待插入结点的数据\n"); // 提示信息

printf("如果输入-1表示插入过程结束\n");// 提示信息

scanf("%d",&temp); // 输入待插入结点数据

} // 循环体结束

if (root==null) // 如果根结点为空

printf("这是一棵空树。\n");// 输出空树信息

else // 根结点不为空

print(root); // 调用print函数,输出二叉树内容

} // 主函数结束

ad