算法基本特征:输入,输出,有穷性,确定性,可行性
算法时间复杂度:在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。算法的时间复杂度也就是算法的时间量度,记为T(n)= O(f(n))。它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,f(n)是问题规模n的某个函数。
大O阶推导:
- 用常数1取代运行时间中的所有加法常数
- 在修改后的运行次数函数中,只保留最高阶项
- 如果最高阶项存在且不是1,则去除与这个项相乘的常数
常见时间复杂度:常数阶o(1),线性阶o(n),平方阶o(n^2),对数阶o(logn),nlogn阶,立方阶,指数阶
耗费时间依次是:o(1) < o(logn) < o(n) < o(nlogn) < o(n^2) < o(n^3) < o(2^n) < o(n!) < o(n^n)
对数阶:
int i = 1, n = 100;
while( i < n ){
i = i * 2;
}
假设有x个2相乘后大于或等于n,则退出循环。
2的x次方 = n, 则x = log2(n),所以时间复杂度为o(logn)
链表
头指针:头指针指链表指向第一个结点的指针,若链表有头结点,则是指向头节点的指针;头指针具有标识作用,所以常用头指针冠以链表的名字(指针变量的名字);无论链表是否为空,头指针均不为空;头指针是链表的必要元素。
头节点:头结点是为了操作的统一和方便而设立的,放在第一个元素结点之前,其数据域一般无意义(但可存放长度);有了头节点,对在第一个元素节点进行插入和删除操作和对其他节点的操作就统一了。头节点不是必须的。
头插法建立单链表:把新加进的元素放在表头后的第一个位置(先让新节点的next指向头结点之后,再让头节点的next指向新节点)
尾插法
单链表的整表删除
- 声明结点p, q
- 将第一个结点赋值给p,下一个结点赋值给q
- 循环执行释放p并将q赋值给p的操作
单链表和顺序存储比较:
*存储分配方式:*顺序存储结构用一段连续的存储单元依次存储线性表的数据元素;单链表采用链式存储,用任意存储单元存放元素。
时间性能:****查找:顺序o(1) 单链表o(n) ****插入和删除:顺序o(n) 单链表先确定位置,再o(1)
空间性能:顺序存储结构需要预先分配存储空间,容易造成空间浪费,单链表反之。
静态链表
用数组描述的链表叫做静态链表(游标实现法)
静态链表初始化:
Status InitList(StaticLinkList space){
int i;
for (i = 0; i < MAXSIZE - 1; i++)
space[i].cur = i+1;
space[MAXSIZE-1].cur = 0;
return OK;
}
说明
1.数组第一个和最后一个元素做特殊处理,不存数据
2.未使用的数组元素称为备用链表
3.数组第一个元素的cur存放备用链表第一个结点的下标
4.数组的最后一个元素的cur存放第一个有数值的元素的下标,相当于单链表的头节点的作用
静态链表的插入操作
将未被使用过的以及已经被删除的分量用游标连成一个备用的链表,插入时,可以从备用链表上取得第一个结点作为待插入的结点。
代码:
静态链表的删除操作
第一个元素的游标指向备用链表的第一项====>
1.删除在L中的第i个元素
Status ListDelete(StaticLinkList i, int i){
int j, k;
if (i < 1 || i >ListLength(L)){
return Error;
}
k = MAX_SIZE - 1;
for (j = 1; j <= i-1;j++){
k = L[k].cur;
}
j = L[k].cur;
L[k].cur = L[j].cur;
Free_SLL(L,j);
return OK;
}
2.将下标为k的空闲结点回收到备用链表
void Free_SLL(StaticLinkList space, int k){
space[k].cur = space[0].cur;
space[0].cur = k;
}
优点:在插入删除时,不需要移动元素,改进了顺序存储插入和删除的缺点
缺点:没有解决连续存储分配带来的表长难以确定的问题;失去了顺序存储随机存取的特性
循环链表
将单链表中终端节点的指针由空指针改为指向头节点,就让单链表形成一个环,头尾相接,成为单循环链表,简称循环链表。
若终端节点用尾指针rear指示,则查找终端节点是o(1),开始节点是rear->next->next,也是O(1)
初始化:
插入:
删除:
返回结点所在位置:
约瑟夫问题
双向链表
双向链表的循环链表
双向链表的插入
双向链表的删除