学习视频:小甲鱼
程序设计=数据结构+算法
数据结构:表示关系,如物理结构、逻辑结构
- 物理结构:顺序结构、链表结构
- 逻辑结构:集合结构、线性结构、树形结构
算法:解决特定问题的步骤。
目录
算法效率的度量方法:
事前分析法:抛弃计算机的硬件差异等,算法的复杂度与算法的好坏和问题输入的规模两者有关。算法复杂度关注最高项的阶次,不关注最高项的常数和非最高项。
- 时间复杂度:T(n)=O(1)常数阶、O(n)线性阶、O(n^2)平方阶、O(log2n)对数阶等。循环的时间复杂度等于循环体的复杂度乘以该循环运行的次数。平均运行时间、最坏运行时间。
- 空间复杂度:对存储空间的要求。可以用空间复杂度的增加去降低时间复杂度。
线性表:
基本概念:
由零个或多个数据元素组成的有限序列。除了第一个和最后一个元素之外,每个元素有且只有一个前驱和后继元素。可以为空表。
数据类型:一组具有相同性质的数的集合或...。如int类型。
抽象数据类型:ADT,简单来说,将数据类型和相关操作捆绑在一起。
线性表的抽象数据类型:ADT 线性表List DATA 线性表内容 Operation 如元素的插入、查询、删除等 endADT
顺序存储结构:
线性表的顺序存储结构:线性表的顺序存储是指在内存中用地址连续的一块存储空间顺序存放线性表的各元素。在程序设计语言中,一维数组在内存中占用的存储空间就是一组连续的存储区域,因此,顺序存储的数据区域就是用一维数组来表示的。
- 需要三个属性,存储空间的起始位置data+线性表的最大存储容量+线性表的当前长度length。
- 地址计算方式:从1开始,已知第一个数据元素的存储位置和类型所占毕节大小可以计算得到任意一个元素的位置,所以存储的时间性能为O(1),成为随机存储结构。
- 常见存O(1)、读O(1)、插入O(n)、删除O(n)等动作。注意:删除表中的第i个元素,意味着删除数组data中的第i-1个元素。
- 优缺点:从上面复杂度的分析就容易得到,优点:无需额外的存储空间来表示元素之间的逻辑关系,可以快速存取任意位置的元素。缺点:插入和删除操作需要移动大量的元素,当线性表长度变化较大时,难以确定存储空间的容量,容易造成存储空间的碎片。
链式存储结构:
线性表的链式存储结构:数据(数据域)+指针(指针域)=数据元素(存储映像、结点),因为每个结点中只包含一个指针域,所以成为单链表。如下图:Head为头指针,然后可能有头结点,接下来是第一个结点...(图参考:https://blog.csdn.net/weixin_41413441/article/details/79063738)
头指针:指向下一个结点(头结点或者第一个结点)的存储位置。头指针为链表的必要元素,无论链表是否为空。头指针具有标识作用,所以常用头指针冠以链表的名字。
头结点:数据域一般无意义(当然有些情况下也可存放链表的长度、用做监视哨等等)。那么看似域头指针非常相似,为什么引入头结点呢?头结点是为了操作的统一和方便而设立的,如对第一个元素结点前插入或者删除第一个结点,操作与对其他结点的操作统一了。
单链表的读取O(n)、工作指针后移思想、插入和删除O(n)。插入和删除的基本思想:查找第i个元素,插入或者删除。
效率比较:在查找和修改方面,顺序存储复杂度更低;在插入和删除方面,顺序存储和链式存储的复杂度相同;但是很明显,对于插入或者删除数据越频繁的操作,单链表的效率优势就越明显。
单链表的整表创建:(初始化空链表L--建立一个带头结点的单链表--循环实现后继结点的赋值和插入)
- 头插法:总是插入到头指针之后;比较简单,但是生成的结点与输入的顺序相反。
- 尾插法:需要定义一个尾指针r,保证循环插入到r之后。
单链表的整表删除:一个结点一个结点的删除。
综上,顺序存储和链式存储在存储方式、时间性能、空间性能方面各有千秋。
静态链表:
静态链表:在没有指针类型的程序语言中用数组描述链表,也称为游标实现法。
基本概念:
如图所示,数组的元素都是由两个数据域组成,data和cur。数据域data用来存放数据元素,而游标cur相当于单链表中的next指针,存放该元素的后继元素在数组中的下标。有两个不存放data的需要注意的元素,数组的第一个元素,即下标为0的元素的cur存放备用链表(未使用的数组元素)的第一个结点的下标;而数组的最后一个元素的cur则存放第一个有数值的元素的下标,相当于单链表的头结点作用。cur为0,表示无指向。
基本操作:插入和删除
优缺点总结:
- 优点:插入和删除时,只需要修改游标。不需要移动元素,改进了顺序存储中需要移动大量的缺点。
- 缺点:没有解决连续存储分配(数组)带来的表长难以确定的问题。而且失去了顺序存储结构随机存取的特性。
腾讯面试题
查找中间结点并输出。
- 传统方法:先找到链表的长度,再查找中间结点。
- 巧妙方法:利用快慢指针,当快指针查找到最后元素的时候,慢指针查找到中间结点。
//查看链表的中间结点值
void GetMidNode(LinkList *L){
LinkList p,p2;
p=(*L)->next;
p2=(*L)->next;
while(p2->next !=NULL){
if(p2->next->next !=NULL){//注意判断最后特殊情况
p=p->next;
p2=p2->next->next;
}
else{//注意判断最后特殊情况
p2=p2->next;
}
}
printf("链表的中间结点为:%d\n",p->data);
}
单链表小结
进行程序练习
注意:
*L=(LinkList)malloc(sizeof(Node)); /* 产生头结点,并使L指向此头结点 */
srand(time(0)); /* 初始化随机数种子 */
循环链表:头指针、尾指针
判断单链表中是否有环
约瑟夫问题、魔术师发牌问题、拉丁方阵问题
双向链表: