BUAA 2021春数据结构 期中复习 2线性表

对于线性表的基本操作:

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.顺序存储结构、链式存储结构、索引结构和散列结构

  • 4
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值