线性表相关算法总结

最近在复习考研数据结构的算法,把自己做过的觉得有意思的算法做个总结。本文均采用C语言形式的算法,并且本文算法会以考研算法的流程走,即先写算法思想,再上代码。考研算法比较偏向于算法思想,所以代码部分可能存有伪代码元素,不一定能正确运行。但是关键是掌握这个算法思想,这个才是重中之中。

1、给定两个链表,编写算法找出两个链表的公共结点。
(好像是剑指offer的一道原题)

算法思想:两个链表中有公共结点的话,那么从第一个公共节点开始,后面的结点都是相同的,不可能在出现分叉(我的理解是链表中的val和next都相同)。又由于两个链表的长度不一定一样,那么先在较长链表上遍历k个结点,再同步遍历两个链表,保证两个链表同时达到最后一个结点,这样也就保证了能够同时到达第一个公共结点。

LinkList SearchFirst(LinkList L1,LinkList L2){
    //获取两个链表长度
    int len1=getLength(L1);
    int len2=getLength(L2);
    //用于指向较长较短链表的第一个结点
    LinkList longlist,shotlist;
    
    if(len1>len2){
        longlist = L1->next;
        shouList = L2->next;
        int dist = len1 - len2;
    }else{
        longlist = L2->next;
        shouList = L1->next;
        int dist = len2 - len1;
    }
    
    while(dist--)
        longlist = longlist->next;
        
    while(longlist!=null){
        if(longlist == shoulist)
        return longlist;
        else{
            longlist = longlist->next;
            shoutlist = shouList->next;
        }
    }
    
    //没有公共结点
    return null;
}

2、链表返回倒数第k个数的位置的值

算法思想一:先遍历链表得出链表总产长度,然后返回链表第n-k+1个节点的数据域

int searchK1(LinkList L,int k){
    int i = 0,j = 0;
    LNode *p = L->next;
    while(p!=null){
        i++;
        p = p->next;
    }
    p = p->next;
    while(j<i-k){
        j++;
        p = p->next;
    }
    return p->data;
}

算法思想二:定义两个指针p,q,初始时两个都指向链表的第一个个元素结点,首先让p沿链表移动k个节点,然后再让p,q两个指针同时沿链表遍历结点。当p指针移动到最后一个结点时,q所指向的结点即为倒数第k个结点。

int searchk2(LinkList L,int k){
    LNode *p = L->next,*q = L->next;
    int count = 0;
    while(p!=null){
        if(count<k)
            count ++;
        else
            q = q->next;
        p = p->next;
    }
    if(count<k){//表长小于k,查找失败
        exit(0);
    }
    else{
        return q->data;
    }
}

3、将两个递增单链表合并成一个非递增单链表

算法思想:两个链表已经按元素之递增次序排序,将其合并时,均从第一个结点起进行比较,将小的结点链入链表中,同时后移工作指针。该问题要求结果链表按元素值递减排序,故采用头插法。比较结束后,可能会有一个链表非空,此时用头插法将剩余链表插入新链表中。

void Merge(LinkList *La,LinkList *Lb){
    LinkList *r,
    *pa = La->next;
    *pb = Lb->next;
    La->next = null;//La作为结果链表的头指针,先将结果链表初始化为空
    while(pa && pb){//两链表都不为空时循环
        if(pa->data <= p->data){
            r = pa->next;//r暂存pa的后继结点指针,以免丢失
            pa->next = La->next;//头插法核心代码一
            La->next = pa;//头插法核心代码二
            pa = r;
        }else{
            r = pb->next;//r暂存pa的后继结点指针,以免丢失
            pb->next = La->next;//头插法核心代码一
            La->next = pb;//头插法核心代码二
            pb = r;
        }
    }
    if(pa)
        pb = pa;//通常情况下会剩余一个链表非空,处理剩余部分
    while(pb){
        r = pb->next;//r暂存pa的后继结点指针,以免丢失
        pb->next = La->next;//头插法核心代码一
        La->next = pb;//头插法核心代码二
        pb = r;
    }
    
    free(Lb);
    
}

4、将带头结点的单链表进行逆置

算法思想:逆置链表初始化为空,依次从原链表中删除结点,将删除的节点依次插入到逆置链表中(用头插法插入),如此循环直至原链表为空

void reverse(LinkList *head){
    LinkList *r,
    *p = head->next;//指针p指向原链表的第一个结点
    head->next = null;//这一步是初始化逆置链表操作
    while(p){
        r = p->next;//r暂存p后面的结点,以免丢失
        p->next = head->next;//头插法核心代码一
        head->next = p;//头插入核心代码二
        p = r;
    }
}

小结:通过3和4可以得出,要将一个链表的值从后面排到前面,可以用头插法实现其链表的逆置。

5、有两个有序链表,如何以最优的方式找出二者的交集,并且把它存在第一链表中

算法思想:采用归并的思想,设置两个工作指针pa,pb,对两个链表进行归并扫描,只有同时出现在两个集合中的元素才链接到结果链表中,结果只保留一个,其他节点全部释放。当一个链表遍历完毕之后,释放另一个链表的全部结点。

LinkList Union(LinkList *La,LinkList *Lb){
    LinkList *pa = La->next,
    *pb = Lb->next,
    *pc = La;
    LNode *q;
    
    while(pa && pb){
        if(pa->data == pb->data){
            pc->next = pa;
            pc = pa;
            pa = pa->next;
            q = pb;
            pb = pb->next;
            free(q);
        }
        else if(pa->data<pb->data){
            q = pa;
            pa = pa->next;
            free(q);
        }
        else{
            q = pb;
            pb = pb->next;
            free(q);
        }
    }
    //while循环结束
    while(pa){//Lb遍历完,La还未结束
        q = pa;
        pa = pa->next;
        free(q);
    }
    while(pb){//La遍历完,Lb还未结束
        q = pb;
        pb = pb->next;
        free(q);
    }
    pc->next = null;
    free(Lb);
    return La;
    
}

6、两个整数递增有序序列A、B分别有n,m个元素,求第k大的数,要求最佳时间复杂度

算法思想:符合归并排序的思想,直接将A、B进行归并排序,然后在新的数组里面寻找第k大的元素,时间复杂度为(max(m,n))

int Merge(int a[],int b[],int k){
    int i=0,j=0,m=o;
    while(i<a.length && j<b.length){
        if(a[i]<=b[j])
            c[m++]=a[i++];
        else
            c[m++]=b[j++];
    }
    while(i<a.length)
        c[m++]=a[i++];
    while(j<b.length)
        c[m++]=b[j++];
        
    return c[k-1];
}

7、设有一个线性表,存放在一维数组a[0…n-1]中,编程将数组中的每一个元素循环右移k位,要求只用一个辅助单元,时间复杂度为O(n)

算法思想:
先把0–n-k-1的部分置逆。再把n-k–n-1部分置逆。最后再把数组0—n-1置逆。

验证算法:
a(0)–a(n-k-1)移动到最终位置a(k)–a(n-1). a(n-k)–(n-1)移动到最终位置为a(0)—a(k-1).
a(0)–a(n-k-1)置逆以后为a(n-k-1) – a(0). 再把数组a(n-k)–a(n-1)置逆时,
a(n-k-1) – a(0)后面变为a(n-1)—a(n-k). 整个数组在置逆,变成a(n-k)----a(n-1) a(0)----a(n-k-1)。所以上面算法正确。

算法实现:
1、实现设置两个指针,分别设置为置逆部分的头尾部分,向中间移动,直到相等。移动一次就交换头尾指针的元素。
2、第一次0–n-k-1的部分置逆。再把n-k–n-1部分置逆。最后再把数组0—n-1置逆。辅助空间为O(1).时间复杂度为O(n)

//反转数组部分,也就是逆置
void Reverse(int R[],int l,intr)
    {
    int i,j;
    int temp;
         for(i=l,j=r;i<j;++i,--j)
             {
             temp=R[i];
             R[i]=R[j];
            R[j]=temp;
            }
    }
    void  RCR(int a[],int n,int k)
    {
          if(k<=0||k>=n)
         cout<<"ERROR"<<endl;
     else
       {
        Reverse(R,0,n-k-1);
        Reverse(R,n-k,n-1);
        Reverse(R,0,n-1);
       }
    }

总结:此题略有难度,需要画图配合着理解

8、已知线性表(a1,a2,…an)按顺序存于内存中,每个元素都是整数,试设计用最少的时间把所有负数的元素都移到正数前面的算法

算法思想:要求重排n个元素且以顺序存储的结构存储线性表,使得所有负数元素全部在正数前面,这不就符合快速排序的思想吗?直接采用快速排序来实现,值得注意的是,算法中提出暂存的第一个元素(枢轴)并作为排序比较的标准,比较的标准以元素是否为负数

int ReArrange(SeqList a,int n){
    int low = 0,high = n-1;
    int t = a[low];//枢轴元素,只是暂存,不作为比较对象
    while(low<high){
        while(low<high && a[high]>=0)
            high--;
        a[low] = a[high];
        while(low<high && a[low]<0)
            low++;
        a[high] = a[low];
    }
    a[low]=t;//将枢轴元素放到最终位置
}

你可能感兴趣的:(数据结构和算法)