第2章 线性表 线性表抽象数据类型 顺序表 主要知识点 单链表 循环单链表 循环双向链表 静态链表 设计举例.

Slides:



Advertisements
Similar presentations
《C语言程序设计》复习
Advertisements

第10章 结构体与链表 本章要点: 结构体类型与结构体变量的定义 结构体变量的引用与初始化 结构体数组 链表处理 共用体类型和枚举类型
计算机三级考试C语言上机试题专题.
“八皇后”问题 崔萌萌 吕金华.
数据结构概论 第2章 线性表 董黎刚 浙江工商大学信电学院
计算机硕士专业基础—C语言 赵海英
第二章 线性表 1 线性表的逻辑结构及其基本操作 2 线性表的顺序存储结构 3 线性表的链式存储结构 4 静态链表 5 应用实例.
第二章 线性表.
第2章 线性表 2.1 线性表的概念及运算 2.2 线性表的顺序存储 2.3 线性表的链式存储 2.4 一元多项式的表示及相加.
数据结构与算法 数据结构与算法实验
第九章 系 统 安 全 性 9.1 结构体 9.2 结构体型数组  9.3 结构体型指针 9.4 内存的动态分配 9.5 共用体
第4章 鏈結串列(Linked Lists) 4-1 動態記憶體配置-(6) 4-2 鏈結串列的基礎-(7)
Linked List Operations
第3章 栈和队列 3.1 栈 3.2 队列 3.3 应用.
單向鏈結串列 Singly Linked Lists.
第2章 线性表 线性结构 是一个数据元素的有序集合。.
第4章 串 串的基本概念和C语言的串函数 串的存储结构 动态数组实现的顺序串 串的模式匹配算法——BF算法 主要知识点.
第6章 佇列(Queues) 6-1 佇列的基礎 6-2 佇列的表示法 6-3 環狀佇列 6-4 雙佇列.
第7章 结构体、联合体和枚举类型 本章导读 本章主要知识点 《 C语言程序设计》 (Visual C++ 6.0环境)
C语言程序设计 第十二章 位运算.
C语言程序设计 课程 第5章 数组 主讲:李祥 博士、副教授 单位:软件学院软件工程系.
選擇排序法 通訊一甲 B 楊穎穆.
第11章 查找 主要知识点 查找的基本概念 静态查找表 动态查找表 哈希表.
佇列 (Queue).
資料結構 第5章 佇列.
第8章 圖形結構(Graphs) 8-1 圖形的基本觀念 8-2 圖形的表示法 8-3 圖形的走訪 8-4 擴張樹
补充内容 结构体 概述 定义结构体类型和定义结构体变量 结构体变量的引用 结构体变量的初始化 指针与结构体 用typedef定义类型的别名.
1、基本概念 2、插入排序 3、交换排序 4、选择排序 5、归并排序 6、基数排序 7、外部排序
If … else 選擇結構 P27.
第12章 樹狀搜尋結構 (Search Trees)
STRUCTURE 授課:ANT 日期:2010/5/12.
计算概论 第十八讲 C语言高级编程 结构与习题课 北京大学信息学院.
第2章 线性表 2.1 线性表的基本概念 2.2 线性表的顺序存储 2.3 线性表的链式存储 2.4 线性表的应用 2.5 有序表 本章小结.
第十章 C高级程序应用—链表* 10.1链表的基本概念 10.2单向链表 10.3双向链表 10.4应用举例.
第5章 堆疊(Stacks) 5-1 堆疊的基礎 5-2 堆疊的表示法 5-3 堆疊的應用 - 運算式的計算與轉換
第5章 堆疊(Stacks) 5-1 堆疊的基礎 5-2 堆疊的表示法 5-3 堆疊的應用 - 運算式的計算與轉換
作弊是否很有诱惑性? 上堂课已经讲了 作业不一定在两个小时里都能完成 答疑没有一个人? 作弊是有记录的 心理系很多同学集体作弊,让人震惊
第3章 堆栈和队列 堆栈 堆栈应用 队列 队列应用 优先级队列 主要知识点.
第 3 讲 线性表(一).
C语言 程序设计基础与试验 刘新国、2012年秋.
第二章 线性表.
資料結構與C++程式設計進階 堆疊與佇列(Stack & Queue) 講師:林業峻 CSIE, NTU 6/ 21, 2010.
第一章 绪论.
陈海明 副教授 信息学院 计算机系 电子信息类非计算机专业选修课 程序设计实践 陈海明 副教授 信息学院 计算机系
内容回顾 线性表的定义和特点 线性表的顺序存储及查找、插入和删除.
多维数组与指针 用指针变量可以指向一维数组中的元素,也可以指向多维数组中的元素。但在概念上和使用上,多维数组的指针比一维数组的指针要复杂一些。 1. 多维数组元素的地址 先回顾多维数组的性质,可以认为二维数组是“数组的数组”,例 : 定义int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}};
THE C PROGRAMMING LANGUAGE
第9章 内部排序 9.1 概述 9.2 插入排序 9.3 快速排序 9.4 选择排序 9.5 归并排序 9.6 基数排序
資料結構 第4章 堆疊.
王玲 第 2 章 线性表 王玲 2019/2/25.
第三节 堆及其应用.
3.1 线性表及逻辑结构 3.2 线性表的顺序存储 3.3 线性表的链式存储 3.4 链式存储结构的应用
自我參考結構 (self-reference – 1)
7.1 广义表的概念 广义表是n(n≥0)个数据元素组成的序列,其中每个数据元素或是单个数据元素(简称原子),或仍然是一个广义表 。
第十章 结构体与链表 西安工程大学.
資料結構與C++程式設計進階 排序與搜尋 講師:林業峻 CSIE, NTU 6/ 14, 2010.
第六章 树和二叉树 学习要点 理解树的定义和基本术语,重点了解二叉树的定义、性质、存储结构; 掌握二叉树遍历的递归算法及它的典型运算;
Main() { Dfas Asdfasf fasdfa } #include <stdio.h> void main( ) {
Chap 5 函数 5.1 计算圆柱体积 5.2 使用函数编写程序 5.3 变量与函数.
本教學投影片係屬教科書著作之延伸,亦受著作權法之保護。
第二章 线性表.
第8章 图 8.1 图的基本概念和基本操作 8.2 图的邻接矩阵存储结构 8.3 图的邻接表存储结构 8.4 图的其他存储结构
第 六 讲 栈和队列(一).
第13章 文 件.
程式設計--linear search 通訊一甲 B 楊穎穆.
第4章 鏈結串列(Linked Lists) 4-1 動態記憶體配置-(6) 4-2 鏈結串列的基礎-(7)
第三章 线性表 3.1 线性表的类型定义 3.2 顺序存储的线性表 3.3 链式存储的线性表 3.4 有序表 3.5 顺序表和链表的综合比较.
Chapter 2 Entity-Relationship Model
第7章 图.
函式庫補充資料 1.
Presentation transcript:

第2章 线性表 线性表抽象数据类型 顺序表 主要知识点 单链表 循环单链表 循环双向链表 静态链表 设计举例

2.1 线性表抽象数据类型 1.线性表的定义 线性表是一种可以在任意位置插入和删除数据元素操作、由n(n≥0)个相同类型数据元素a0, a1,…, an-1组成的线性结构。 线性结构:

2.线性表抽象数据类型 数据:{ a0, a1, … , an-1 }, ai的数据类型为DataType (1) ListInitiate(L) 初始化线性表 (2) ListLength(L) 求当前数据元素个数 (3) ListInsert(L,i,x) 插入数据元素 操作: (4) ListDelete(L,i,x) 删除数据元素 (5) ListGet(L,i,x) 取数据元素 { a0, a1, … , an-1 }表示数据元素有次序关系,简称序列。

2.2 线性表的顺序表示和实现 顺序存储结构的线性表称作顺序表 1.顺序表的存储结构 实现顺序存储结构的方法是使用数组。数组把线性表的数据元素存储在一块连续地址空间的内存单元中,这样线性表中逻辑上相邻的数据元素在物理存储地址上也相邻。数据元素间的逻辑上的前驱、后继逻辑关系就表现在数据元素的存储单元的物理前后位置上。 顺序表的存储结构如图所示

list a0 a1 a2 a3 a4 a5 … 1 2 3 4 5 6 size=6 MaxSize-1 1 2 3 4 5 6 size=6 MaxSize-1 其中a0 ,a1, a2等表示顺序表中存储的数据元素,list表示顺序表存储数据元素的数组,MaxSize表示存储顺序表的数组的最大存储单元个数,size表示顺序表当前存储的数据元素个数。 typedef struct { DataType list[MaxSize]; int size; } SeqList;

2.顺序表操作的实现 (1)初始化ListInitiate(L) void ListInitiate(SeqList *L) { L->size = 0; /*定义初始数据元素个数*/ } (2)求当前数据元素个数ListLength(L) int ListLength(SeqList L) { return L.size; }

(3)插入数据元素ListInsert(L, i, x) int ListInsert(SeqList *L, int i, DataType x) { int j; for(j = L->size; j > i; j--) L->list[j] = L->list[j-1]; /*依次后移*/   L->list[i] = x; /*插入x*/ L->size ++; /*元素个数加1*/ return 1; }

(4)删除数据元素ListDelete(L, i, x) int ListDelete(SeqList *L, int i, DataType *x) { int j; *x = L->list[i]; /*保存删除的元素到x中*/   for(j = i +1; j <= L->size-1; j++) L->list[j-1] = L->list[j]; /*依次前移*/ L->size--; /*数据元素个数减1*/ return 1; }

int ListGet(SeqList L, int i, DataType *x) (5)取数据元素ListGet(L, i, x) int ListGet(SeqList L, int i, DataType *x) { if(i < 0 || i > L.size-1) { printf("参数i不合法! \n"); return 0; } else { *x = L.list[i]; return 1;

3.顺序表操作的效率分析 时间效率分析: 算法时间主要耗费在移动元素的操作上,因此计算时间复杂度的基本操作(最深层语句频度) T(n)= O(移动元素次数) 而移动元素的个数取决于插入或删除元素的位置i. 若i=size,则根本无需移动(特别快); 若i=0,则表中元素全部要后移(特别慢); 应当考虑在各种位置插入(共n+1种可能)的平均移动次数才合理。

同理可证:顺序表删除一元素的时间效率为: 设Pi是在第i个存储位置插入一个数据元素的概率,顺序表中的数据元素个数为n,当在顺序表的任何位置上插入数据元素的概率相等时,有Pi=1/(n+1),则 插入时的平均移动次数为: n(n+1)/2÷(n+1)=n/2≈O(n) 同理可证:顺序表删除一元素的时间效率为: T(n)=(n-1)/2 ≈O(n)

顺序表中的其余操作都和数据元素个数n无关,因此,在顺序表中插入和删除一个数据元素的时间复杂度为O(n),其余操作的时间复杂度都为O(1) 主要优点是算法简单,空间单元利用率高; 主要优缺点 主要缺点是需要预先确定数据元素的最大个数,插入和删除时需要移动较多的数据元素。

4.顺序表应用举例 例:编程实现如下任务:建立一个线性表,首先依次输入数据元素1,2,3,…,10,然后删除数据元素5,最后依次显示当前线性表中的数据元素。要求采用顺序表实现,假设该顺序表的数据元素个数在最坏情况下不会超过100个。

实现方法: 1、采用直接编写一个主函数实现。 2、利用已设计实现的抽象数据类型模块。(存放在头文件名为SeqList 实现方法: 1、采用直接编写一个主函数实现。 2、利用已设计实现的抽象数据类型模块。(存放在头文件名为SeqList.h中,通过 #include “SeqList.h” ) 程序设计如下: #include <stdio.h> #define MaxSize 100 typedef int DataType; #include "SeqList.h"

程序运行结果: 1 2 3 4 6 7 8 9 10 void main(void) { SeqList myList; int i , x;   ListInitiate(&myList); for(i = 0; i < 10; i++) ListInsert(&myList, i, i+1); ListDelete(&myList, 4, &x); for(i = 0; i < ListLength(myList); i++) ListGet(myList, i, &x); } 程序运行结果: 1 2 3 4 6 7 8 9 10

2.3 线性表的链式表示和实现 1.单链表的结构 (1)单链表中构成链表的结点只有一个指向直接后继结点的指针域。其结构特点:逻辑上相邻的数据元素在物理上不一定相邻。

或 数据域:存储元素数值数据 指针域:存储直接后继的存储位置 结点结构如图示: 指针域 数据域 next data 或 数据域:存储元素数值数据 指针域:存储直接后继的存储位置

(2)头指针、头结点和首元结点的区别 示意图如下: a0 head a1 … an ^ 头指针 头结点 首元结点

头指针是指向链表中第一个结点(或为头结点、或为首元结点)的指针; 头结点是在链表的首元结点之前附设的一个结点;数据域内只放空表标志和表长等信息,它不计入表长度。 首元结点是指链表中存储线性表第一个数据元素a0的结点。 (3)带头结点单链表和不带头结点单链表的比较

1).在带头结点单链表第一个数据元素前插入结点 p a0 a1 an-1 ∧ … head data next x s (a) 插入前 p a0 a1 an-1 ∧ … head data next x s (b) 插入后

2).删除带头结点单链表第一个数据元素结点 p a0 a1 an-1 ∧ … head data next

3).在不带头结点单链表第一个数据元素前插入结点 a0 a1 an-1 ∧ … head x s (a) 插入前 a0 a1 an-1 ∧ … head x s (b) 插入后

4).在不带头结点单链表其他数据元素前插入结点 p ai-1 a0 ai an-1 ∧ … head data next x s ×

5).删除不带头结点单链表第一个数据元素结点 a0 a1 an-1 ∧ … head data next × 6).删除不带头结点单链表其他数据元素结点 p ai-1 a0 ai an-1 ∧ … head data next × ai+1

结论: (1)带头结点单链表无论在第一个数据元素结点前插入,还是在其他结点前插入,操作方法一样;而不带头结点单链表在第一个数据元素结点前插入,和在其他结点前插入,操作方法不一样 (2)删除操作和插入操作类似 (3)设计带头结点单链表的算法时,头指针参数可设计成输入型参数;设计不带头结点单链表的算法时,头指针参数必须设计成输出型参数 (4)因此,带头结点单链表的算法设计简单

2.单链表的操作实现 结点定义: typedef struct Node { DataType data; struct Node *next; } SLNode (1)初始化ListInitiate(head) void ListInitiate(SLNode **head) { *head = (SLNode *)malloc(sizeof(SLNode)); (*head)->next = NULL; }

(2)求当前数据元素个数ListLength(head) int ListLength(SLNode *head) { SLNode *p = head; int size = 0; while(p->next != NULL) { p = p->next; size ++; } return size;

(3)插入ListInsert(head, i, x) int ListInsert(SLNode *head, int i, DataType x) { SLNode *p, *q; int j;   p = head; j = -1; while(p->next != NULL && j < i - 1) { p = p->next; j++; } if(j != i - 1) { printf(“插入位置参数错!”); return 0; q = (SLNode *)malloc(sizeof(SLNode)); q->data = x; q->next = p->next; p->next = q; return 1;

说明:①要在带头结点的单链表第i(0 ≤ i ≤ size)个结点前插入一个存放数据元素x的结点,首先要在单链表中寻找到第i-1个结点并由指针p指示,然后动态申请一个结点存储空间并由指针q指示,并把数据元素x的值赋予新结点的数据元素域(即q->data = x),最后修改新结点的指针域指向ai结点(即q->next = p->next),并修改ai-1结点的指针域指向新结点q(即p->next = q)。 ②循环条件由两个子条件逻辑与组成,其中子条件p->next != NULL保证指针所指结点存在,子条件j < i - 1保证最终让指针p指向ai-1结点。

(4)删除ListDelete(head, i, x) int ListDelete(SLNode *head, int i, DataType *x) { SLNode *p, *s; int j;   p = head; j = -1; while(p->next != NULL && p->next->next!= NULL && j < i - 1) { p = p->next; j++; } if(j != i - 1) { printf(“插入位置参数错!”); return 0;   s = p->next; *x = s->data; p->next = p->next->next; free(s); return 1; } 

说明:要在带头结点的单链表中删除第i(0 ≤ i ≤ size - 1)个结点,首先要在单链表中寻找到第i-1个结点并由指针p指示,然后让指针s指向ai结点(即s = p->next),并把数据元素ai的值赋予x(即*x = s->data),最后把ai结点脱链(即p->next = p->next->next),并动态释放ai结点的存储空间(即free(s))。删除过程如图2-14所示。图中的①对应算法中的删除语句。

(5)取数据元素ListGet(head, i, x) int ListGet(SLNode *head, int i, DataType *x) { SLNode *p; int j;   p = head; j = -1; while(p->next != NULL && j < i) { p = p->next; j++; }   if(j != i) { printf(“取元素位置参数错!”); return 0;   *x = p->data; return 1;

(6)撤消单链表Destroy(head) void Destroy(SLNode **head) { SLNode *p, *p1;   p = *head; while(p != NULL) { p1 = p; p = p->next; free(p1); } *head = NULL;

4.单链表操作的效率分析 单链表的插入和删除操作不需移动数据元素,只需比较数据元素。因此,当在单链表的任何位置上插入数据元素的概率相等时,在单链表中插入一个数据元素时比较数据元素的平均次数为: 删除一个数据元素时比较数据元素的平均次数为: 另外,单链表求数据元素个数操作的时间复杂度为O(n)。

和顺序表相比 主要优点是不需要预先确定数据元素的最大 个数,插入和删除操作不需要移动数据元素; 单链表 主要缺点是查找数据元素时需要顺序进行,不能像顺序表那样随机查找任意一个数据元素。另外,每个结点中要有一个指针域,因此空间单元利用率不高。而且单链表操作的算法也较复杂。 和顺序表相比,单链表的优点和缺点正好相反。

5.单链表应用举例 例:编程实现如下任务:建立一个线性表,首先依次输入数据元素1,2,3,…,10,然后删除数据元素5,最后依次显示当前线性表中的数据元素。要求采用单链表实现。 #include <stdio.h> #include <stdlib.h> #include <malloc.h> typedef int DataType; #include "LinList.h"

程序运行结果: 1 2 3 4 6 7 8 9 10 void main(void) { SLNode *head; int i , x; ListInitiate(&head);  for(i = 0; i < 10; i++) ListInsert(head, i, i+1) ; ListDelete(head, 4, &x) ; for(i = 0; i < ListLength(head); i++) { ListGet(head, i, &x) == 0) ; printf(“%d ”, x); } Destroy(&head); 程序运行结果: 1 2 3 4 6 7 8 9 10

2.4 循环单链表 循环单链表是单链表的另一种形式,其结构特点是链表中最后一个结点的指针域指向整个链表的第一个结点,从而使链表形成一个环。它的优点是从链尾到链头比较方便。循环单链表也有带头结点和不带头结点两种结构。 一个带头结点的循环单链表如下图示:

P->next!=NULL 改为 p- >next != head an-1 … head (a) 空链表 (b) 非空链表 程序设计: p!=NULL 改为 p!=head P->next!=NULL 改为 p- >next != head

2.5 双向链表 双向链表是每个结点除后继指针域外还有一个前驱指针域,它有带头结点和不带头结点,循环和非循环结构,双向链表是解决查找前驱结点问题的有效途径。 prior data next 结点结构如图示: 下图是带头结点的循环双向链表的结构,可见,其前驱指针和后继指针各自构成自己的循环单链表。

a0 a1 an-1 head (b) 非空链表 … head (a) 空链表 在双向链表中: 设指针p指向第i个数据元素结点,则p->next指向第i+1个数据元素结点,p->next->prior仍指向第i个数据元素结点,即p->next->prior=p;同样p->prior->next=p。

a0 ai an-1 head … x s ai-1 × ④ ① ② ③ p 循环双向链表的插入过程如图示:

ai+1 ai an-1 head … ai-1 × ① ② p 删除过程如图示:

2.6 静态链表 在数组中增加一个(或两个)指针域用来存放下一个(或上一个)数据元素在数组中的下标,从而构成用数组构造的单链表。因为数组内存空间的申请方式是静态的,所以称为静态链表,增加的指针称做仿真指针。结构如下: A B C E ∧ head D (a) 常规链表

(b) 静态链表1 (b) 静态链表2 A 1 B 2 C 3 D 4 E -1 ┇ data next maxSize-1 A 2 E maxSize-1 A 2 E -1 B 4 D 1 C 3 ┇ (b) 静态链表2 data next maxSize-1

2.7 设计举例 例2-4 设计一个顺序表的删除函数,把顺序表L中的数据元素x删除。 算法思想:首先,找到要删除元素的位置,然后,从这个位置到最后一个元素,逐个前移,最后,把元素个数减1。

int ListDataDelete(SeqList *L, DataType x) { int i, j;   for(i = 0; i < L->size; i++) if(x == L->list[i]) break;  if(i == L->size) return 0; else { for(j = i; j < L->size; j++) L->list[j] = L->list[j+1]; L->size--; return 1; }

例2-5 构造一个顺序表的删除算法,把顺序表L中所有值相同的数据元素x全部删除。

int ListMoreDataDelete(SeqList *L, DataType x) { int i, j; int tag = 0; for(i = 0; i < L->size; i++) { if(x == L->list[i]) { for(j = i; j < L->size-1; j++) L->list[j] = L->list[j+1]; L->size--; i--; tag = 1; } }  return tag;

例2-6 设头指针为head,并设带头结点单链表中的数据元素递增有序,编写算法将数据元素x插入到带头结点单链表的适当位置上,要求插入后保持单链表数据元素的递增有序。 算法思想:从链表的第一个数据元素结点开始,逐个比较每个结点的data域值和x的值,当data小于等于x时,进行下一个结点的比较;否则就找到了插入结点的合适位置,此时申请新结点把x存入,然后把新结点插入;当比较到最后一个结点仍有data小于等于x时,则把新结点插入单链表尾。

void LinListInsert(SLNode *head, DataType x) { SLNode *curr, *pre, *q; curr = head->next; pre = head; while(curr != NULL && curr->data <= x) { pre = curr; curr = curr->next; } q = (SLNode *)malloc(sizeof(SLNode)); q->data = x; q->next = pre->next; pre->next = q;

算法设计说明: (1)在循环定位插入位置时,循环条件必须首先是curr != NULL,然后是curr->data <= x。如果次序颠倒,则当curr为空(即等于链表结束标记NULL)时,将因为curr->data不存在而出错;次序不颠倒时,当curr等于NULL时将退出循环,不会进行后边条件curr->data <= x的比较。 (2)当比较到最后一个结点仍有data小于等于x时,此时有pre指向最后一个结点,curr等于NULL,则上述算法把新结点插入到了单链表尾作为了单链表新的表尾结点。

例2-7 设head为单链表的头指针,并设单链表带有头结点,编写算法将单链表中的数据元素按照数据元素的值递增有序的顺序进行就地排序。 算法思想:在例2-6算法的基础上,再增加一重循环即可实现全部数据元素的排序。由于此时的排序过程没有申请新的结点空间,所以这样的排序算法满足就地排序,即不增加新的内存空间的设计要求。

具体实现过程是:把头指针head所指单链表置空(即初始时head所指单链表仅包含一个头结点),把去掉头结点的原单链表(设由头指针p指示)中的数据元素逐个重新插入head所指单链表中。每次插入从head所指单链表的第一个数据元素结点开始,逐个比较head所指单链表每个结点的data域值和p所指单链表的当前第一个数据元素结点的data域值,当前者小于或等于后者时,用head所指单链表的下一个结点进行比较;否则就找到了插入结点的合适位置,从p所指单链表中取下当前第一个数据元素结点插入到head所指单链表的合适位置。这样的过程一直进行到p所指单链表为空时结束。

void LinListSort(SLNode *head) { SLNode *curr, *pre, *p, *q; p = head->next; head->next = NULL; while(p != NULL) { curr = head->next; pre = head; while(curr != NULL && curr->data <= p->data) { pre = curr; curr = curr->next; } q = p; p = p->next; q->next = pre->next; pre->next = q;

作业 1) 习题2-1,2-2 ,2-3 习题2-4,2-10,2-14 2) 习题2-16 3) 习题2-5,2-6,2-15 4)习题2-20,2-21 5)习题2-18,2-22