230 likes | 375 Views
链表及其应用. 关于单链表掌握以下知识: 为什么需要单链表 如何通过指针和结构体的定义实现单链表结构 单链表常用操作的实现方法 : 建立、遍历、插入、删除、查找、. 9.10.2 链表概念的引入. 处理一个班的学生,一般使用结构体数组, 需预留足够大的数组空间. 带来两个问题 : 1. 多余的元素占用的空间浪费,可动态申请数组空间解决, 2. 但有时系统不能满足过大的连续存储空间的要求。. 解决: 使用链表结构 , 一次只需要一个结构体的连续空间, 就像现实生活中的 链子 ,由若干 环节 组成,
E N D
链表及其应用 • 关于单链表掌握以下知识: • 为什么需要单链表 • 如何通过指针和结构体的定义实现单链表结构 • 单链表常用操作的实现方法:建立、遍历、插入、删除、查找、
9.10.2 链表概念的引入 处理一个班的学生,一般使用结构体数组, 需预留足够大的数组空间 带来两个问题: 1. 多余的元素占用的空间浪费,可动态申请数组空间解决, 2. 但有时系统不能满足过大的连续存储空间的要求。 解决:使用链表结构,一次只需要一个结构体的连续空间, 就像现实生活中的链子,由若干环节组成, 在 C++ 语言中用结构体实现链表环节, • 对于一个班,有多少个学生,就动态地生成多少个结构体, • 如何来连接它们呢?用指针 • 将它们连接成一个链表结构, 画图表示,单向链表 --->
p, stu 10101 p+1 stu[0] “Li Lin” 18 88 ‘M’ 10102 “Zhang Fun” p+2 stu[1] 19 ‘M’ 99 10104 “Wang Min” 20 stu[2] ‘F’ …... 100 2. 指向结构体数组元素的指针 student stu[10]= { {10101, "Li Nin",18,'M',88}, {...}, {...}, ...... }; student *p; p = stu; for(i=0; i<10; i++, p++) cout << p->num << p->name << p->score << endl ;
head …... …... …... …... …... 0 结尾标志 NULL 空指针 (环节) (节点) 结构体 为形成链表,结构体要增加一个成员, 即指针,指向下一个结构体节点。 如上图是一个单向链表。
struct student //定义节点结构体类型 { int num; /* 学号 */ char name[20]; /* 姓名 */ int age; /* 年龄 */ char sex; /* 性别 */ int score; /* 成绩 */ student *next; }; 优点:解决了上述两个问题 1. 需要多少结构体,就动态申请多少个结构体空间, 比数组实现节约空间。 2. 每个结点未必连续存放,通过指针将各个结点连接起来, 这样对大片连续存储区的要求降低。 3. 另外,对链表的操作如插入、删除等, 也变得比较简单了。(不需要挪动数组元素空间了)
链表上的数据结点为: struct node { int data; node *next; }; 9.10.3 链表的常用算法 ① 创建链表(有序链表、无序链表) ② 遍历链表(依次访问链表的每一个结点) 查找结点,释放链表各节点的空间 ③ 删除结点 ④ 插入结点
p1 p2 head NULL [例] 动态申请,建立链表,输出各结点数据,最后循环依次释放各结点空间。 #include <iostream.h> struct node{ int data; node *next; }; void main( ) { 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; } } 输出:1000 1001 1002
head p2 p1 [例] 链表常用算法介绍 1.创建无序链表:循环输入数据,若数值不为-1,新建一结点并连接到链表尾部。 加入结点时, 分三种情况 ①首结点的建立 ②中间结点的加入 ③尾结点的处理 head 指向链表首结点 p2 指向建立过程中的链表尾结点 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;就可以实现在尾部插入新结点
动态 动态 过程 过程 演示 演示 2. 遍历链表 • 单链表的遍历:从头指针开始,顺着各个指针,依次访问链表中的各个结点 • 关键是确定遍历的条件,即何时循环,何时终止 • 根据单链表的最后一个指针域为空,可以让一个工作指针指向当前结点,不断后移,如果该指针为空,则链表遍历结束 • 关键代码: • for(p = head; p; p = p->next) • printNode(p->data); • 单链表的查找:在单链表中搜索是否存在这样的结点,其数据域或数据域的某一项等于给定待搜索的值。 • 查找返回:若存在则返回指向该结点的指针,否则返回空指针 • 关键代码: • while(p&&!equal(p->data,data,1)) • p=p->next; 该函数是根据结点的数据域类型定义的一个输出函数 该函数是根据结点的数据域类型定义的一个判断元素值是否相等的函数
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); //释放有序链表空间 }