Transcript Document

链表及其应用
• 关于单链表掌握以下知识:
– 为什么需要单链表
– 如何通过指针和结构体的定义实现单链表结
构
– 单链表常用操作的实现方法:建立、遍历、插
入、删除、查找、
2015/4/13
1
9.10.2 链表概念的引入
处理一个班的学生,一般使用结构体数组,
需预留足够大的数组空间
带来两个问题:
1. 多余的元素占用的空间浪费,可动态申请数组空间解决,
2. 但有时系统不能满足过大的连续存储空间的要求。
解决:使用链表结构,一次只需要一个结构体的连续空间,
就像现实生活中的链子,由若干环节组成,
在 C++ 语言中用结构体实现链表环节,
ª 对于一个班,有多少个学生,就动态地生成多少个结构体,
ª 如何来连接它们呢?用指针
ª 将它们连接成一个链表结构, 画图表示,单向链表 --->
2. 指向结构体数组元素的指针
p, stu
student stu[10]=
{ {10101, "Li Nin",18,'M',88},
{...},
{...},
......
p+1
};
student *p;
p = stu;
for(i=0; i<10; i++, p++)
cout << p->num
<< p->name
<< p->score
<< endl ;
p+2
10101
“Li Lin”
18
‘M’
88
10102
“Zhang Fun”
19
‘M’
99
10104
“Wang Min”
20
‘F’
100
…...
stu[0]
stu[1]
stu[2]
(环节)
(节点)
结构体
head
…...
…...
…...
…...
…...
0
为形成链表,结构体要增加一个成员,
即指针,指向下一个结构体节点。
如上图是一个单向链表。
结尾标志
NULL
空指针
struct student //定义节点结构体类型
{ int num; /* 学号 */
char name[20]; /* 姓名 */
int age; /* 年龄 */
char sex; /* 性别 */
int score; /* 成绩 */
student *next;
};
优点:解决了上述两个问题
1. 需要多少结构体,就动态申请多少个结构体空间,
比数组实现节约空间。
2. 每个结点未必连续存放,通过指针将各个结点连接起来,
这样对大片连续存储区的要求降低。
3. 另外,对链表的操作如插入、删除等,
也变得比较简单了。(不需要挪动数组元素空间了)
9.10.3 链表的常用算法
① 创建链表(有序链表、无序链表)
② 遍历链表(依次访问链表的每一个结点)
查找结点,释放链表各节点的空间
③ 删除结点
④ 插入结点
链表上的数据结点为:
struct node
{
int data;
node *next;
};
[例] 动态申请,建立链表,输出各结点数据,最后循环
依次释放各结点空间。
#include <iostream.h>
struct node{ int data;
node *next; };
void main( )
head
{ node *head, *p1, *p2;
head = new node;
p1 = new node; p2 = new node;
head->data = 1000; head->next = p1;
p1->data = 1001; p1->next = p2;
p2->data = 1002; p2->next = NULL;
while(head!=NULL)
{ cout << head->data << endl;
p1=head; head = head->next;
delete p1;
}
}
p1
p2
NULL
输出:1000
1001
1002
[例] 链表常用算法介绍
1.创建无序链表:循环输入数据,若数值不为-1,
新建一结点并连接到链表尾部。
加入结点时, 分三种情况
①首结点的建立
head
②中间结点的加入
③尾结点的处理
p2
head 指向链表首结点
p2 指向建立过程中的链表尾结点
p1 指向新开辟的结点
p1
程序
node *Create( ) //返回值: 链表的首指针
{ node *p1, *p2, *head; int a; head = NULL;
cout <<"正在创建一条无序链表...\n";
cout <<"请输入一个整数, 以 -1 结束: ";
cin >> a;
while( a != -1 ) // 循环输入数据,建立链表
{
p1 = new node; p1->data = a;
解释
if(head==0 ) // ①建立首结点
{ head=p1; p2=p1; }
else // ②处理中间结点
{ p2->next=p1; p2=p1; }
cout <<"请输入一个整数, 以 -1 结束: ";
cin >> a;
}
if(head != 0) p2->next=0;//③处理尾结点, 可能第1次就输入 -1
return(head); //返回创建链表的首指针
单链表的建立:
• 无论是哪一种建立方法,共同的操作是:
– 最主要的是指向第一个结点的头指针,有时需要设一个指向
最后一个结点的尾指针以方便操作,注意头指针该如何修改
– 逐个申请结点空间
– 对每个结点的数据域赋值
– 每个结点的指针域如何赋值取决于建立方法
– 将新结点加入到链表中,即修改链表中某一个结点的指针域
• 后插法的关键是:
– 新结点的指针域值一定为空,因为它是新的最后一个结点
– 头指针只要修改一次,即在空链状态下插入第一个点时修改
– 保证tail指针始终指在当前链表的最后一个结点处,即新结点
p插入链表之后,要做赋值: tail=p; 以后通过tail->next=p;就
可以实现在尾部插入新结点
2015/4/13
10
2. 遍历链表
 单链表的遍历:从头指针开始,顺着各个指针,依次访问链表中
的各个结点
• 关键是确定遍历的条件,即何时循环,何时终止
• 根据单链表的最后一个指针域为空,可以让一个工作指针指向当前
该函数是根据结点
结点,不断后移,如果该指针为空,则链表遍历结束
的数据域类型定义
• 关键代码:
的一个输出函数
动
过
演示
• for(p = head; p; p = p->next)
态
程
•
printNode(p->data);
 单链表的查找:在单链表中搜索是否存在这样的结点,其数据域
或数据域的某一项等于给定待搜索的值。
该函数是根据结点的数据
• 查找返回:若存在则返回指向该结点的指针,否则返回空指针
域类型定义的一个判断元
• 关键代码:
素值是否相等的函数
• while(p&&!equal(p->data,data,1))
•
p=p->next;
2015/4/13
11
动
过
演示
态
程
2. 遍历链表(输出链表上各个结点的值)
void Print( const node *head ) // 不允许通过 head 修改
{
// 链表中结点的值
const node *p;
p=head;
cout << "输出链表中各结点数据:";
while( p!=NULL )
{
cout <<setw(4) << (p->data) ;
p=p->next;
}
cout << endl;
}
2. 遍历链表(续)(查找结点 )
node * Search( const node *head, int x )
// 不允许通过head修改链表中结点的值
{
const node *p; // 不允许通过p修改它指向的结点的值
p=head;
while( p!=NULL )
{
if(p->data == x) return p; //若找到,则返回该结点指针
p = p->next;
}
return NULL; //若找不到,则返回空指针
}
3. 删除一个结点
删除链表上具有指定值的第一个结点
• 首先要用遍历的思想查找该值的结点是否在单链表存在
• 一定要有一个指向待删除结点前趋结点位置的指针(p2)以
便删除
删除结点时, 分三种情况
①原始链表为空链表 //无结点可删
②待删除的结点是原始链表的首结点 // head 指针变化
③删除其他结点
删
过
演示
除
程
// 函数功能:
删除第1个值为num的结点,返回新链表的首指针。
node *Delete_one_node( node *head, int num )
{
node *p1, *p2;
if( head == NULL) // 链表为空,处理情况①
{ cout << "链表为空,无结点可删!\n";
return(NULL);
}
p1 = head;
while(p1->data != num && p1->next != NULL )
{
// 循环查找待删除结点
p2 = p1;
p1 = p1->next; // p2指向的结点在p1指向的结点之前
}
if(p1->data == num) // 找到待删除结点,由p1指向
{
if(p1==head) // 若找到的结点是首结点,处理情况②
head=p1->next;
else
// 找到的结点不是首结点,处理情况③
p2->next = p1->next;
delete p1;
cout << "删除了一个结点!\n";
}
else // 未找到待删除结点
cout<<num<<"链表上没有找到要删除的结点!\n";
return(head);
}
4. 释放链表
void Delete_chain( node *head //释放已创建的链表结点空间)
{
node *p;
while( head)
{
p=head;
head=head->next;
delete p;
}
}
5. 插入结点
把一个结点插入链表,使链表结点数据保持升序
插入结点时, 分四种情况
①原链表为空链表;②插入在链表首结点之前;
③插入在链表中间;④插在链表尾结点之后。
// 函数功能:将p指向的结点插入链表, 结果链表保持有序。
返回值是新链表的首指针。
node *Insert(node *head, node *p) //
{
node *p1, *p2;
if(head == NULL) // 原链表为空链表,对应情况①
{
head = p;
p->next = NULL;
return(head);
}
p1 = head;
while( (p->data) > (p1->data) && p1->next != NULL )
{
// 寻找待插入位置
p2 = p1; p1 = p1->next;
// p2指向的结点在p1指向的结点之前
}
}
if( (p->data) <= (p1->data) ) // 插在p1之前
{
p->next = p1;
if(head == p1)
head = p;
// 插在链表首部,对应情况②
else
p2->next = p; // 插在链表中间,对应情况③
}
else
// 插在链表尾结点之后,对应情况④
{
p1->next = p;
p->next = NULL;
}
return(head);
序
过
插
演示
程
6. 创建有序链表
node *Create_sort(void)
{
node *p1, *head=0;
int a;
cout << "产生一条排序链表, 请输入数据, 以-1结束: ";
cin >> a;
while( a!=-1 )
{
p1=new node;
p1->data = a;
head=Insert(head,p1);
cin >> a;
}
return(head);
}
7. 主函数,测试上述函数
#include <iostream.h>
#include "node_def.h"
#include "create.h"
#include "print_search.h"
#include "delete.h"
#include "insert.h"
#include "deletechain.h"
#include "create_sort.h"
void main( )
{
node *head;
int num;
head=Create( );
//建立一条无序链表
Print(head);
//输出链表
续:
cout << "输入待删除结点上的整数:\n";
cin >> num;
head=Delete_one_node(head, num);
Print(head);
//删除一个结点后,输出链表
cout << "输入要查找的整数:\n";
cin >> num;
if(Search(head, num)!=NULL)
cout<<num<<" 在链表中\n";
else
cout<<num<<" 不在链表中\n";
Deletechain(head);
//释放上述链表空间
head=Create_sort( );
//建立一条有序链表
Print(head);
//输出链表
Deletechain(head);
//释放有序链表空间
}