对于线性表的基本操作:
1.创建
2.求长度
3.检索第i个元素
4.查找位置
5.存入元素
6.插入元素
对于一个长度为n的线性表,在第i-1个元素与第i个元素之间插入一个元素item。
对于顺序表:
正常算法:1.将n-i依次后移一个元素
2.插入,表长+1
异常情况:1.不满足0 <= i <= n 2.表已经满了
时间复杂度:设pi为插入到i位置前面的概率。若概率相等 则pi = 1/(n+1)(考虑了插入到最后) 而移动次数为n+1-i,求和有ave(T)=n/2
7.删除元素
删除第i个元素
对于顺序表:同上
时间复杂度为 ∑ (n-i)/n = (n-1)/ 2
8.排序
9.销毁
10.复制
11.合并
12.分解
一.顺序表
连续的内存单元,需要提前分配地址,不适合动态数据处理。
顺序表(数组)的查找算法:
1.顺序查找
时间复杂度O(n)
int searchElem(ElemType list[],int n,ElemType item){
for(i=0;i<n;i++){
if(list[i]==item) return i;
}
return -1;
}
2.二分查找(binary search)
朴素查找算法时间复杂度O(n),二分查找时间复杂度O(log n)
假设数据集按照从小到大排列(非严格单调递增),其核心思想为:
确定当前集合中点mid,若item小于(大于)mid数值,则在前半(后半)部分查找。若item等于mid数值,则返回mid,若最后没有元素查找,则查找失败。
最简单的二分查找算法demo:
此处的left=0,right=length-1
int biSearch(int list[],int left,int right,int item){
while(left<=right){
int mid = left + (right - left)/2;
if(list[mid]==item) return mid;
else if(list[mid]<item) left = mid + 1;
else if(list[mid]>item) right = mid - 1;
}
return -1;
}
但是发明KMP算法的Knuth大佬曾经如此评价二分查找:
Although the basic idea of binary search is comparatively straightforward, the details can be surprisingly tricky...
因此需要注意二分查找处的细节:
出循环的条件是 left < right 还是 left <= right
left = mid + 1 还是 left = mid
这些都和程序需要的具体条件有关系
由于查找的是一个闭区间[left,right],因此只要是left<=right,这个区间都是有意义的,并不需要退出循环。
mid已经比较过,因此下一个区间的边界不应该包含mid
但是这个二分查找有个致命的缺陷:如果有重复的数字,不能保证查到的是第几个。
如果要寻找最左边的item,就需要即使找到了mid,也要把区间左移。
int biSearch(int list[],int left,int right,int item){
while(left<=right){
int mid = left + (right - left)/2;
if(list[mid]==item) right = mid; //关键所在
else if(list[mid]<item) left = mid + 1;
else if(list[mid]>item) right = mid - 1;
}
return left;
}
但是这样,有一个致命的错误:right一直等于mid等于left,导致不能出循环,所以这种条件要去掉等号。
由于去掉等号,所以考虑的实际上是一个开区间,这里需要输入的right就要是n而不是n-1了!
并且需要考虑最后找不到值的条件。
int biSearch(int list[],int left,int right,int item){
while(left<right){ //注意
int mid = left + (right - left)/2;
if(list[mid]==item) right = mid; //关键所在
else if(list[mid]<item) left = mid + 1;
else if(list[mid]>item) right = mid - 1;
}
return (list[left]==item)?left:-1; //这里left right都可以,因为出循环的条件是left=right
}
二.链表:
1.创建一个长度为n的链表
Nodeptr createList(int n){
Nodeptr p,q,list=NULL;
for(int i=0;i<n;i++){
q=(Nodeptr)malloc(sizeof(Node));
q->data=read();
q->link=NULL
if(list == NULL) list=p=q;//如果是第一个节点,第一个节点地址赋值给list
else p->link=q;//如果不是第一个节点,上一个节点p指向这个节点q
p=q;//用p记录这个节点
}
return list;
}
2.求线性表的长度
int getLen(Nodeptr list){
int len=0;
for(Nodeptr p=list;p!=NULL;p=p->link){
n++;
}
return n;
}
3.在头节点前加入一个新的头节点
Nodeptr insertFirst(Nodeptr list,ElemType item){
Nodeptr p = (Nodeptr)malloc(sizeof(Node));
p->data = item;
p-link = list;
return p;
/*
使用return p而不用list = p是因为此处的list与外界的list并不相同,在这一点出指针参数和非指针参数的是一样的
*/
}
4.在p节点(已知)后面插入一个item
void insertNode(Nodeptr p,ElemType item){
Nodeptr q=(malloc)(sizeof(Node));
q->data=item;
q->link=p->link;
p->link=q;
}
5.在第n个节点后插入item
void insertNodeN(Nodeptr list,int n,ElemType item){
Nodeptr p=list;
for(int i=0;i<n-1;i++){
if(p->link==NULL) error;
else p=p->link;
}
Nodeptr q=(Nodeptr)malloc(sizeof(Node));
q-data=item;
q->link=NULL;
q->link = p->link;
p->link = q;
}
对于上述的链表插入元素操作,需要注意特殊情况:链表为空,插入位置在头节点之前
6.删除p指向的节点,也要考虑删除的是否是第一个节点:
Nodeptr delNode(Nodeptr list,Nodeptr list p){
if(p==list){ //考虑第一个节点的情况
list = list ->link;
free(p);
}
else{
for(Nodeptr r=list;r->link!=p && r->link!=NULL;r=r->link)//寻找前驱节点r
;
if(r->link!=NULL){
r->link = p->link;
free(p);
}
}
return list;/*由于头节点list可能会改变 所以需要返回list
函数调用需要使用list = delNode(list,p)*/
}
有时候,也会在表头使用一个标志节点,不存放数据,称为哑巴节点(dummy node),这样链表的头节点就不会改变。
7.循环链表
尾节点指向头节点
可以用来解决约瑟夫环(Josephu Ring)问题
可以用来存储动态数据的最后n个数据(不断读取并删除)
8.双向链表
link变为llink(左指针)和rlink(右指针)
同时也有双向循环链表
第三次作业
设lista,listb分别为两个有序链表(升序)的第1个链结点的指针,将这两个有序链表合并为一个有序链表,并设合并后的链表的第一个链结点的指针为listc.
LinkList MERGELIST(LinkList lista,LinkList listb)
{
LinkList listc,p=lista,q=listb,r;
if(lista->data<=listb->data){
listc=lista;
r=lista;
p=lista->link;
}
else{
listc=listb;
r=listb;
q=listb->link;
}
while(p!=NULL&&q!=NULL){
if(p->data<=q->data){
r->link=p; 【 正确答案: r->link=p;】
r=p;
p=p->link; 【 正确答案: p=p->link;】
}
else{
r->link=q; 【 正确答案: r->link=q;】
r=q;
q=q->link; 【 正确答案: q=q->link;】
}
}
r->link= p 【 正确答案: p 或 q==NULL 或 p!=NULL 或 (q==NULL) 或 (p!=NULL)】?p:q;
return listc;
}
数据的存储结构通常有 【正确答案: D】。
A.顺序存储结构和链式存储结构
B.顺序存储结构、链式存储结构和索引结构
C.顺序存储结构、链式存储结构和散列结构
D.顺序存储结构、链式存储结构、索引结构和散列结构