大话数据结构学习之(二) 线性表
来源:互联网 发布:软件行业税负 编辑:程序博客网 时间:2024/06/05 23:57
1. 线性表的顺序存储结构
用一段连续的存储单元一次存储线性表的数据元素。
结构代码:
const int MAXSIZE = 20; // 存储空间初始分配量typedef int ElemType; // ElemType类型根据实际情况而定,这里假设为inttypedef struct{ElemType data[MAXSIZE]; // 数组存储数据元素int length; // 线性表当前长度}SqList;
这里,我们发现描述顺序存储结构需要三个属性:
- 存储空间的起始位置:数组data。
- 线性表的最大存储容量:数组长度MAXSIZE。
- 线性表的长度:length。
1.1 数组长度与线性表长度的区别:
数组长度是存放线性表存储空间的长度,线性表长度是线性表中数据元素的个数。任意时刻线性表的长度应小于等于数组长度。
其存取性能为O(1),通常把具有这一特点的存储结构成为随机存储结构。
1.2 顺序存储结构的插入与删除
1.2.1 插入操作:
// 初始条件:顺序线性表L已经存在,1<=i<=ListLength(L)// 操作结果:在L中第i个位置之前插入新的数据元素e,L长度加1Status ListInsert(SqList *L, int i, ElemType e){int k;if (L->length == MAXSIZE) // 顺序线性表已满return ERROR;if (i<1 || i>L->length+1) // 当i不在范围内时,注意是length+1return ERROR;if (i<=L->length) // 当插入数据位置不在表尾{for (k=L->length-1; k>=i-1; k--) // 将要插入位置后数据元素后移一位{L->data[k+1] = L->data[k];}}L->data[i-1] = e;L->length++;return Ok;}
1.2.2 删除操作:
// 初始条件:顺序线性表L已经存在,1<=i<=ListLength(L)// 操作结果:删除在L中第i个位置之前的数据元素e,L长度减1Status ListDelete(SqList *L, int i, ElemType *e){int k;if (L->length == 0) // 顺序线性表为空return ERROR;if (i<1 || i>L->length) // 当i不在范围内return ERROR;if (i<L->length) // 当删除位置不在表尾{for (k=i-1; k<L->length; k++) // 将要删除位置后数据元素前移一位{L->data[k] = L->data[k+1];}}L->length--;return Ok;}
复杂度
最好情况O(1),最差情况O(n),平均复杂度为O((n-1)/2) = O(n)。
这说明,它比较适合于元素个数不太变化,而更多是存取数据的应用。
1.3 线性表顺序存储结构的优缺点:
优点:
- 无需为表中元素之间的逻辑关系而增加额外的存储空间
- 可以快速存取表中任意位置的元素
- 插入删除操作需要移动大量元素
- 当线性表长度变化较大时,难以确定存储空间容量
- 造成存储空间“碎片”
2. 线性表的链式存储结构
链表中第一个结点的存储位置叫做头指针。线性链表的最后一个结点指针为“空”(通常用NULL或“^”符号表示)。
有时,我们为了更加方便的对链表进行操作,会在单链表的第一个结点前附设一个结点,称为头结点。头结点的数据域可以不存储任何信息,也可以存储如线性表长度等附加信息,头结点的指针域存储指向第一个结点的指针。
头结点与头指针的异同:
头指针:
- 头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针
- 头指针具有标识作用,所以常用头指针冠以链表的名字
- 无论链表是否为空,头指针均不为空。头指针是链表的必要元素
- 头结点是为了操作的统一和方便而设立的,放在第一个元素的结点之前,其数据域一般没有意义(也可以放链表长度)
- 有了头结点,对在第一个结点前插入和删除第一个结点,其操作与其他结点的操作就统一了
- 头结点不一定是链表的必要元素
// 线性表的单链表存储结构typedef struct Node{ElemType data;struct Node *next;}Node;typedef struct Node *LinkList; // 定义LinkList
2.1 单链表的读取
// 初始条件:顺序线性表L已经存在,1<=i<=ListLength(L)// 操作结果:用e返回L中第i个数据元素的值Status GetElem(LinkList L, int i, ElemType *e){int j;LinkList p;p = L->next; // 让p指向链表L的第一个结点j = 1;while(p && j<i){p = p->next;++j;}if (!p || j>i){return ERROR; // 第i个元素不存在}*e = p->data; // d取第i个元素的数据return Ok;}
说白了,就是从头开始找,直到第i个元素为止。时间复杂度取决于i的位置,i=1时不需要遍历,而i=n时,则遍历n-1次才可以。
2.2 单链表的插入与删除
2.2.1 单链表的插入
// 初始条件:顺序线性表L已经存在,1<=i<=ListLength(L)// 操作结果:在L中第i个位置之前插入新的数据元素e,L长度加1Status ListInsert(LinkList *L, int i, ElemType e){int j;LinkList p,s;p = *L;j = 1;while(p && j<i){p = p->next;++j;}if (!p || j>i){return ERROR; // 第i个元素不存在}s = (LinkList)malloc(sizeof(Node));// 生成新结点s->data = e;s->next = p->next;// 将p的后继结点赋值给s的后继p->next = s; // 将s赋值给p的后继return Ok;}对于单链表的表头和表尾的特殊情况,操作是相同的。
2.2.2 单链表的删除
// 初始条件:顺序线性表L已经存在,1<=i<=ListLength(L)// 操作结果:删除在L中第i个位置之前的数据元素e,L长度减1Status ListDelete(LinkList *L, int i, ElemType *e){int j;LinkList p,q;p = *L;j = 1;while(p->next && j<i){p = p->next;++j;}if (!(p->next) || j>i){return ERROR; // 第i个元素不存在}q = p->next;p->next = q->next; *e = q->data; // 将q结点中的数据给efree(q); // 回收此结点return Ok;}
对插入删除数据越频繁的操作,单链表的效率优势就越明显。
2.3 单链表的整表创建
创建单链表的过程就是一个动态生成链表的过程,即从“空表”的初始状态起,依次建立各个元素结点,并逐个插入链表。
2.3.1 头插法
// 随机产生n个元素的值,建立带表头结点的单链线性表L(头插法)void CreateListHead(LinkList *L, int n){LinkList p;int i;srand(time(0)); // 初始化随机数种子*L = (LinkList)malloc(sizeof(Node)); (*L)->next = NULL; // 先建立一个带头结点的单链表for (i=0; i<n; i++){p = (LinkList)malloc(sizeof(Node)); // 生成新结点p->data = rand()%100+1;// 随机生成100内数字p->next = (*L)->next;(*L)->next = p;// 插入到表头}}
2.3.2 尾插法
// 随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法)void CreateListTail(LinkList *L, int n){LinkList p,r;int i;srand(time(0)); // 初始化随机数种子*L = (LinkList)malloc(sizeof(Node)); r = *L; // r为指向尾部的结点for (i=0; i<n; i++){p = (LinkList)malloc(sizeof(Node)); // 生成新结点p->data = rand()%100+1;// 随机生成100内数字r->next = p;// 将表尾结点的指针指向新结点r = p;// 将当前的新结点定义为表尾结点}r->next = NULL;}
2.4 单链表的整表删除
// 初始条件:顺序线性表L已经存在// 操作结果:将L重置为空表Status ClearList(LinkList *L){LinkList p,q;p = (*L)->next; // p指向第一个结点while(p){q = p->next;free(p);p = q;}(*L)->next = NULL;// 头结点指针域为空return Ok;}
要知道p是一个结点,除了数据域还有指针域,free(p)是对整个结点进行删除和内存释放工作。所以如果程序中直接像下面这样写会出问题的(p的地址域已经被释放)
free(p);
p = p->next;
2.5 单链表结构与顺序存储结构的比较
- 频繁查找,很少进行插入删除操作,宜采用顺序存储结构;反之用单链表。比如游戏开发中,对于用户注册的个人信息,除了注册时插入数据外,绝大多数情况都是读取,所以应考虑用顺序存储结构。而游戏中玩家的武器装备列表,随着玩家的游戏过程中,可能随时增加或删除,单链表就可以大展拳脚了。
- 当线性表中元素个数变化较大或者根本不知道有多大时,最好用单链表结构,这样就不需要考虑存储空间大小问题。若事先知道大致长度,如一年12个月,这种用顺序结构效率会高很多。
2.6 循环链表
对于非空的循环链表如下:
其实循环链表和单链表的主要差异就在于循环的判断条件上,原来是判断p->next是否为空,现在是它不等于头结点,则循环结束。
2.7 双向链表
双向链表是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。
// 线性表的双向链表存储结构typedef struct DulNode{ElemType data;struct DulNode *prior; // 直接前驱指针struct DulNode *next; // 直接后继指针}DulNode, *DuLinkList;
对于双向链表中的一个结点p,它的后继的前驱,以及前驱的后继都是自己。
2.7.1 插入操作
插入操作时,并不复杂,但是顺序很重要,不能写反了。
s->prior = p; // 把p赋值给s的前驱,如图中的1s->next = p->next; // 把p->next赋值给s的后继,如图中2p->next->prior = s; // 把s赋值给p->next的前驱,如图中3p->next = s; // 把s赋值给p的后继,如图中的4
2.7.2 删除操作
p->prior->next = p->next; p->next->prior = p->prior;free(p);
0 0
- 大话数据结构学习之(二) 线性表
- 大话数据结构(二)线性表的基本知识点
- 《大话数据结构》读书笔记(二)——线性表
- 《大话数据结构》——学习笔记(概述&线性表)
- 大话数据结构之三:线性表
- 大话数据结构---线性表
- 数据结构学习笔记(二)------------线性表之链式存储
- 大话数据结构(三)线性表
- 大话数据结构读书笔记(三)-线性表
- linux--线性表(大话数据结构)
- 数据结构学习总结(二) 线性表
- 数据结构之线性表(二)
- (二)数据结构之线性表
- 大话数据结构学习笔记(二)
- 大话数据结构学习笔记(二)
- 《大话数据结构》学习笔记(二)
- 数据结构(二) 线性结构之线性表
- 大话数据结构:线性表(1)
- Spring 注解 @Resource和@Autowired的区别
- C# DataTable详细用法
- QTableWidget用法总结
- 产品经理入门到大神的资料全推荐
- #include<bits/stdc++.h>包含C++的所有头文件
- 大话数据结构学习之(二) 线性表
- qt对所有文字形式的文本使用tr()
- BZOJ 1015 JSOI2008 星球大战 starwar 并查集
- 架构设计模式概要总结
- APS.NET MVC3 + EF5 + SQLSERVER2008 配置环境搭建
- 第三章——装饰者模式
- 计算机系统知识总结
- MongoDB 在Macox 下的安装和启动
- Android_Activity_startActivityForResult和onActivityResult问题