上节课作业的错误:
- 采用顺序存储结构表示数据时,相邻的数据元素的存储地址:一定连续。
- 算法的可行性是指算法中的每一步都可以通过已经实现的基本运算的有限次执行得以实现。
- 确定性:算法的每一步必须有确切的含义,无二义性。算法的执行对应着的相同的输入仅有唯一路径。
1、数据如何存储?
- 数据结构中,数据的基本存储方式主要是利用数组和链表实现的
- 数据的存储跟操作密切相关,存储方式没有最好的,只有最合适的。
一、线性表的逻辑结构
- 每一个线性表有唯一的第一元素
- 每一个线性表有唯一的最后元素
- 除了最后一个元素之外的每一个元素都有唯一后继
- 除了第一个元素外的每一个元素都有唯一前驱
1、线性表的基本操作
线性表的定义:线性表是具有相同数据类型的n个数据元素的有限序列。
1. 线性表的初始化:Init_List(L)
初始条件:表L不存在
操作结果:构造一个空的线性表
2. 求线性表的长度:Length_List(L)
初始条件:表L存在
操作结果:返回线性表中所含元素的个数
3. 取表元:Get_List(L,i)
初始条件:表L存在且1<=i<=Length_List(L)
操作结果:返回线性表L中的第i个元素的值或地址
4. 按值查找:Locate_List(L,x)
初始条件:线性表L存在,x是给定的数据元素
操作结果:返回在L中首次出现的值为x的那个元素的序号或地址,称为查找成功; 否则,在L中未找到值为x的数据元素,返回一特殊值表示查找失败。
5.插入操作:Insert_List(L,i,x)
初始条件:线性表L存在,插入位置正确(1<=i<=n+1,n为插入前的表长)。
操作结果:在线性表L的第i个位置上插入一个值为x 的新元素,原序号为i, i+1, … , n 的数据元素的序号变为i+1,i+2, … , n+1,插入后表长=原表长+1。
6. 删除操作:Delete_List(L,i)
初始条件:线性表L存在,1<=i<=n。
操作结果:在线性表L中删除序号为i的数据元素,删除后使序号为i+1, i+2,…, n 的元素变为序号为i, i+1,…,n-1,新表长=原表长-1
二、线性表的顺序存储及运算实现
1、线性表的顺序存储实现
- 顺序表:在内存中用地址连续的一块存储空间顺序存放线性表各元素。用元素在机内的物理位置相邻表示逻辑相邻关系。
- 一维数组在内存中占用的存储空间就是一组连续的存储区域
定义结构体
表长为last + 1,第一个到第n个数据元素分别存放在data[0]~data[last]中。
//定义结构体
typedef struct{
int data[Maxsize];
int last;
}SeqList;
线性表初始化
将last指针置为 -1,表示表中没有数据元素。因为在判断表空时,L->last = Maxsize - 1;当表空时Maxsize = 0 ,则Maxsize - 1 = -1,所以last = -1时表空。
//初始化表
SeqList *Init_SeqList(){
SeqList *L;
int i;
L = new SeqList;
L->last = i;
return L;
}
插入运算
时间复杂度为:n
//插入表操作,时间复杂度:n
int Insert_SeqList(SeqList *L, int i, int x){
int j;
if(L->last == Maxsize - 1){ //表满
printf("Error: overflow error!\n");
return 0;
}else if(i > L->last + 2 || i < 1){ //下标越界
printf("Error: Index error!");
return 0;
}else{
for(j = L->last;j >= i - 1;j--)
L->data[j + 1] = L->data[j];
L->data[i - 1] = x;
L->last++;
Show_SeqList(L);
return 1;
}
}
有序表插入
有序表插入,插入条件满足之后直接从表后判断大小,L->data大于x就将data向后移动,再向前查找,直到data小于x或者到表头,插入到i+1的位置,因为i的位置是小于x的。
//插入有序表
int Insert_SeqList_ord(SeqList *L, DataType x)
{
int i = L->last ;
if(L->last == MaxSize - 1)
{
printf("表满!\n");
return 0
}
else
{
while(i >= 0 && L->data[i] > x) //向后移动
{
L->data[i + 1] = L->data[i];
i--;
}
L->data[i + 1] = x;
L->last++;
return 1;
}
}
按值查找
查找元素下标,时间复杂度为:n²
//查找元素下标,时间复杂度:n^2
int Location_SeqList(SeqList *L, int x){
int i = 0;
while(i <= L->last && L->data[i] != x)
i++;
if(i > L->last)
return 0;
else
return i;
}
删除运算
时间复杂度:n
//删除表操作,时间复杂度:n
int Delete_SeqList(SeqList *L, int i){
int j;
if(i > L->last + 1 || i < 1){
printf("Error: Index error!"); // 下标越界
return 0;
}else{
for(j = i; j <= L->last; j++)
L->data[j - 1] = L->data[j];
L->last--;
Show_SeqList(L);
return 1;
}
}
线性表的链式存储和运算实现
单链表
链表的结点定义
typedef struct Node{
DataType data; //数据域
struct Node *next; //后继节点的存储地址
}LNode,*LinkList;
建立单链表
链表与顺序表不同,它是一种动态管理的存储结构,链表中的每个结点占用的存储空间不是预先分配的,而是运行时系统根据需求生成的。因此建立单链表从空表开始,每读一个数据元素则申请一个结点。
1、在链表的头部插入结点建立单链表
//建立单链表,将新结点插入链表的头部!当输入为-1时结束插入
LinkList Creat_LinkList1(){
LinkList head;
LNode *s;
int x,flag = -1;
head = new LNode;
head->next = NULL;
printf("请依次输入链表元素(头节点插入):");
scanf("%d", &x);
while(x != flag){
s = new LNode;
s->data = x;
s->next = head->next;
head->next = s;
scanf("%d", &x);
}
printf("创建成功.\n链表长度:%d\n", Length_LinkList(head));
Show_LinkList(head);
return head;
}
2、在单链表的尾部插入结点建立单链表
在头部插入建立单链表简单,但读入的数据元素的顺序与生成的链表中的元素的顺序是相反的,若希望顺序一致,可以采用尾部插入的方法。因此每次是新结点插入到链表的尾部,所以需加入一个指针r,用来始终指向链表中的尾结点,以便能够将新结点插入到链表的尾部。
//建立单链表,将新结点插入链表的尾部!当输入为-1时结束插入
LinkList Creat_LinkList2(){
LinkList head;
LNode *r,*s;
int x, flag = -1;
head = new LNode;
head->next = NULL;
r = head;
printf("请依次输入链表元素(尾节点插入):");
scanf("%d", &x);
while(x != flag){
s = new LNode;
s->data = x;
r->next = s;
r = s;
scanf("%d", &x);
}
r->next = NULL;
printf("创建成功.\n链表长度:%d\n", Length_LinkList(head));
Show_LinkList(head);
return head;
}
求表长
带头节点的单链表求表长算法
//求链表长度
int Length_LinkList(LinkList head){
int i = 0;
LNode *p;
p = head;
while(p->next){
p = p->next;
i++;
}
return i;
}
按值查找操作
时间复杂度:n
//查找数据结点
LNode *Location_LinkList(LinkList head,DataType x){
LNode *p;
p = head->next;
while(p->next->data != x)
p = p->next;
return p;
}
插入
时间复杂度:n
//在i位置插入data为x的结点
int Insert_LinkList(LinkList head, int i, DataType x){
int j = 0;
LNode *p, *s;
p = Get_LinkList(head, i - 1);
if(p == NULL)
return 0;
else{
s = new LNode;
s->data = x;
s->next = p->next;
p->next = s;
return 1;
}
}
删除
时间复杂度:n
/*
删除i处结点
return: 0 删除失败, 1 删除成功
*/
int Del_LinkList(LinkList L, int i){
LNode *p, *s;
p = Get_LinkList(L, i - 1);
if(p == NULL){
printf("Error: Index error.\n");
return 0;
}else{
s = p->next;
p->next = s->next;
delete s;
return 1;
}
}
顺序表和链表的比较
顺序表的优点
- 方法简单,各种高级语言都有数组,容易实现
- 不用为表示结点间的逻辑关系而增加额外的存储开销
- 顺序表具有按元素序号随机访问的特点
缺点
- 在顺序表中做插入、删除操作时,平均移动大约表中一半的元素,因此对数据元素的个数较多的顺序表来说效率低。
- 需要预先分配足够大的存储空间。预先分配过大,可能会导致顺序表后部大量闲置;过小又会造成溢出。
链表的优缺点恰好与顺序表相反。
思考1:如何读取任意数据元素?
已知基地址为LOC(a1),每个元素占用k个存储单元,第i个数据元素的存储地址为?
公式:LOC(ai)=LOC(a1)+(i-1)*k
小结:顺序表是随机的存取结构,计算任意元素存储地址的时间相等,即查找操作的时间性能:O(1)