【计算机本科补全计划】链式存储线性表的一些相关操作

时间:2022-05-12
本文章向大家介绍【计算机本科补全计划】链式存储线性表的一些相关操作,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

正文之前

不管怎么说,好歹是吧王道的第二章看完了!线性表算法写的我都快吐了,不过成果也是有的,可以写一些稍微复杂的算法了!感动,希望尽早达到老师的要求,然后去实验室愉快的写码!!

正文

1、【王道算法】把带头结点的ptrl链表按照奇偶顺序节点分为两个链表A B

void Depart(PtrL ptrl,PtrL A,PtrL B)
{
    A=(List *)malloc(sizeof(List));//分为两个待头结点的链表A 和B
    B=(List *)malloc(sizeof(List));
    PtrL X,Y=ptrl->next;//先预备好,X指向每一次操作的节点,而Y则保护好后面还未参与的链表的头结点
    for (int i=0; Y; i++) // 循环中,因为Y是指向待操作的第一个节点,如果Y指向NULL 那么代表操作完毕,可以退出循环了。
    {
        X=Y;// X来继承Y保护的待操作的第一个节点,也就是Y所指向的节点  待操作==》 进入操作的转换
        Y=Y->next;   //但是在操作之前要先把Y指向下一个节点,也就是第二个待操作节点进入待操作第一序列
        if(i%2==0) // 奇偶判断
        {  // 为偶数就放到A里面 ,均采用头插法,这样会导致输出顺序与原来的顺序相反,不过前面写过逆置函数,直接一用就行。或者改为尾插法。
            X->next=A->next;
            A->next=X;
        }
        else
        {// 奇数就放到B里面
            X->next=B->next;
            B->next=X;
        }
    }
    ShowList(A);
    ShowList(B);

}




void NiZhi(PtrL ptrl)
{
    PtrL x,y;
    x=ptrl->next;
    ptrl->next=NULL;///这里很重要!!不然就成环 疯狂转!!
    while(y)//此处用y直接就可以全部串上去,我那会用y->next 始终没法出来第一个
    {
        y=x->next;
        x->next=ptrl->next;
        ptrl->next=x;
        x=y;
    }
}

2、【王道算法】逆置一个链表

//整体的思想是拆链子,从原来的链子上面一个个把链节点拆下来 然后放到新组合的链子上去。

void NiZhi(PtrL ptrl)
{
    PtrL x,y;
    x=ptrl->next; //保留带头结点的头指针
    ptrl->next=NULL;  //这里很重要!!不然就成环 疯狂转!! 采用尾插法,所以需要下一个等于头结点的下一个指向NULL,不然最后一步的时候会形成循环的链表,那时候输出就会很可怕
    while(y)//此处用y直接就可以全部串上去,我那会用y->next 始终没法出来第一个
    {
        y=x->next;
        x->next=ptrl->next;
        ptrl->next=x;
        x=y;
    }
}


//本文的思想很类似于“把带头结点的ptrl链表按照奇偶顺序节点分为两个链表A B”

3、【王道算法】删除一个链表中的最小值

void Del_MinX(PtrL ptrl)
{
    PtrL X=ptrl->next,minD=ptrl->next;
    int  minX=X->Data;
    while(X->next)
    {
        if(X->next->Data<minX)
        {
            minX=X->next->Data;
            minD=X;
        }
        X=X->next;
    }

    Del(minD);
}//总体来说很经典的顺序查找最小值的办法。效率是O(n)

4、【王道算法】将一个链表反向输出,利用递归方式

void Contrary_Output(PtrL ptrl)
{

    if (ptrl->next!=NULL)//如果链表没有到头,那么就继续递归下去,知道到了最后一个才开始进入下一步
    {
        Contrary_Output(ptrl->next);
    }
    print(ptrl->Data); //第一次执行这一句的是最后一个节点,然后退出最后一次递归,进入倒数第二次,这里的data是倒数第二个节点。

}

5、【王道算法】237循环双链表部分

#include <stdio.h>
#include <stdlib.h>
//注:定义队列结构体及其指针
typedef struct List
{
    int Data;
    struct List *next,*pre;
} List, *PtrL;

void Intilize(PtrL ptrl,int data)
{
    PtrL s=(List *)malloc(sizeof(List));
    s->next=ptrl;
    s->Data=data;
    s->pre=ptrl->pre;
    ptrl->pre->next=s;
    ptrl->pre=s;
}



void ShowList(PtrL ptrl)
{
    PtrL show;
    show=ptrl->next;
       do
       {
        int num=show->Data;
        printf("n##########n#");
        printf("t%dt",num);
        printf("#n");
        printf("##########n");
        printf("t||nt||nt||nt||");
        printf("n   ~~~~~      " );
        printf("n    VVV       " );
        printf("n     V        " );
        printf("n" );
        show=show->next;
       }while(show!=ptrl->next);

    printf("Now Circle is Donennnn");
}



int main()
{
    PtrL ptrl=(List *)malloc(sizeof(List));
    ptrl->Data=0;
    ptrl->pre=ptrl;
    ptrl->next=ptrl;
    Intilize(ptrl, 1);
    Intilize(ptrl, 2);
    Intilize(ptrl, 3);
    Intilize(ptrl, 0);
    Intilize(ptrl, 3);
    Intilize(ptrl, 2);
    Intilize(ptrl, 1);
    ShowList(ptrl);
    //Symmetrical_Judge(ptrl);
}

6、【王道算法】检查双链表是否对称,从头指针开始(需要的话在外围进行一轮循环遍历查看是否对称即可)

void Symmetrical_Judge(PtrL ptrl)
{
    PtrL a=ptrl->next,b=ptrl->pre;//我目前只用了头结点来做对称判断的起点,所以取头结点的前后两个节点开始
    while (a!=b && a->next!=b->pre)  // 存在整个链表是奇数和偶数两种情况,所以,需要两个判定条件,采用和判断,因为一个链表只能有一个情况,都要考虑到所以采用非和模式,这样的话当不满足的时候才会循环,一旦满足一个终止条件,就可以跳出了。这里我一开始错了,用或判断最后无限循环
    {
        if (a->Data==b->Data) 
        {// 如果对称,那么数值自然相等,所以就可以继续往下一个跑了
            a=a->next;
            b=b->pre;
        }
        else 
        {// 一旦不相等,妥妥的就是不对称,可以直接退出了
            printf("This Circle is not symmetrical !nnn");
            return ;
        }
    }
//那么,如果循环遍历期间所有的对称位置的点的值都相等的话,就有该循环双向链表对称判断
     printf("This Circle is symmetrical, very Good!!nnn");
     return;

}

7、【王道算法】237部分单链表算法

#include <stdio.h>
#include <stdlib.h>
//注:定义队列结构体及其指针
typedef struct List
{
    int Data;
    struct List *next;
} List, *PtrL;

void Intilize(PtrL ptrl,int data)
{
    PtrL s=(List *)malloc(sizeof(List));
    PtrL p=ptrl->next->next;
    while(p->next!=ptrl->next)
        p=p->next;
    s->next=p->next;
    p->next=s;
    s->Data=data;
}



void ShowList(PtrL ptrl)
{
    PtrL show;
    show=ptrl->next;
       do
       {
        int num=show->Data;
        printf("n##########n#");
        printf("t%dt",num);
        printf("#n");
        printf("##########n");
        printf("t||nt||nt||nt||");
        printf("n   ~~~~~      " );
        printf("n    VVV       " );
        printf("n     V        " );
        printf("n" );
        show=show->next;
       }while(show->next!=ptrl->next);

    printf("Now Circle is Donennnn");
}




int main()
{
    PtrL ptrl=(List *)malloc(sizeof(List));
    ptrl->Data=0;
    ptrl->next=ptrl; //因为这一句,所以成了循环的单链表,不然就是寻常的单链表
    Intilize(ptrl, 1);
    Intilize(ptrl, 2);
    Intilize(ptrl, 3);
    Intilize(ptrl, 4);
    Intilize(ptrl, 5);
    Intilize(ptrl, 6);
    Intilize(ptrl, 7);
    ShowList(ptrl);

}

8、 【王道算法】删除单链表中位于两个数值之间的所有节点

void Del_Between_2_Num(PtrL ptrl,int f,int d)
{
    PtrL del=ptrl->next,del1,del2;
    int count1,count2;//采用两个整形数判断是否存在这两个数,同时把这两个数所在的节点用del1 del2标记,后面方便删除
    while (del->next)
    {
        if (del->Data==f)
        {
            count1++;
            del1=del;
        }
        if(del->Data==d)
        {
            count2++;
            del2=del;
        }
        del=del->next;
    }

    if (count1&&count2) //如果检测出来两个值都存在,那么就从第一个值所在节点开始删除,停止条件就是del1->next=del2->next,代表中间已经删除干净了。
    {
        while (del1->next!=del2)
            Del(del1);
    }
    else
    {
        return;
    }
}




void print(int num)
{
    printf("n#########n#");
    printf("t%dt",num);
    printf("#n");
    printf("#########n");
}
void Del(PtrL ptrl)
{
    PtrL D;
    D=ptrl->next;
    ptrl->next=D->next;
    print(D->Data);
    free(D);
}

9、【王道算法】逐个删除循环单链表中的最小值直到为空,最后删除表头(太艰难了,因为没怎么处理过单循环链表,所以很多地方用错了结束条件,卡了一个多小时)

#include <stdio.h>
#include <stdlib.h>
//注:定义队列结构体及其指针
typedef struct List
{
    int Data;
    struct List *next;
} List, *PtrL;

void Intilize(PtrL ptrl,int data)
{
    PtrL s=(List *)malloc(sizeof(List));
    if (ptrl->next==NULL)
    {
        ptrl->next=s;
        s->next=s;
        s->Data=data;
    }
    else
    {
        PtrL p=ptrl->next;
        while(p->next!=ptrl->next) //此处是一种变种的尾插法,效率很低!!每插入一次都要遍历一次
            p=p->next;
        s->next=p->next;
        p->next=s;
        s->Data=data;
    }
}

void print(int num)
{
    printf("n#########n#");
    printf("t%dt",num);
    printf("#n");
    printf("#########n");
}

void ShowList(PtrL ptrl)
{
    PtrL show;
    show=ptrl->next;
    do
    {
        int num=show->Data;
        printf("n##########n#");
        printf("t%dt",num);
        printf("#n");
        printf("##########n");
        printf("t||nt||nt||nt||");
        printf("n   ~~~~~      " );
        printf("n    VVV       " );
        printf("n     V        " );
        printf("n" );
        show=show->next;
    }while(show->next!=ptrl->next);

    printf("Now Circle is Donennnn");
}


void Del(PtrL ptrl)
{
    PtrL D;
    D=ptrl->next;
    ptrl->next=D->next;
    print(D->Data);
    free(D);
}

//其实最核心的问题是:我当初定义删除函数的时候,因为图方便只用了一个指针,但是常规的方式里面是有前驱指针的,这也就导致了我的删除有着很大的限制,最后导致了整体的错漏,比如每当到了删除ptrl->next的时候,根本无法动手,要遍历到尾指针来删除,并且头指针也要移动,另外还有就是循环单链表由于尾节点指向头结点,那么在用X->next->Data的时候就会碰到必须要更早的时候判定才行。否则就没法在到了尾节点的时候及时刹车!

void Del_MinX(PtrL ptrl)
{
    PtrL X=ptrl->next,minD=ptrl->next;
    int  minX=X->Data;
    while(X->next->next!=ptrl->next) //我就是忘了这儿是循环链表导致进入了死循环,要给定停止条件.另外有循环链表的特殊性所以要特别注意
    {

        if(X->next->Data<minX)
        {
            minX=X->next->Data;
            minD=X;
        }
        X=X->next;
    }
    if (minD==ptrl->next)//要考虑minX处于头结点的时候的问题,这时候要把minD移位到尾节点去!因为这个时候不好找前驱节点,或者可以用ptrl来代替?!!不能!事实证明,因为这个时候还要把原来的ptrl头结点移位,否则会丢失!
    {
        while(minD->next!=ptrl->next)
            minD=minD->next;
        if (minD->next!=NULL)
        {
            ptrl->next=ptrl->next->next;
            Del(minD);
        }
        else
            free(minD);
    }
    else
        Del(minD);
}
//总体来说很经典的顺序查找最小值的办法。效率是O(n)  但是这里面有坑,那就是如果最小值头指针所指,


void  Del_All(PtrL ptrl)
{
    while(ptrl->next!=ptrl->next->next) 
//错漏在此处,因为最后如果只剩下一个,那么由于前面设置的单循环链表性质,会自己指向自己,那么就会陷入无法删除NULL的情况!!!这是最骚的!!@握草  我真是日了狗了 被这儿卡了一个小时!!!
    {
        Del_MinX(ptrl);
    }
    Del(ptrl);
    free(ptrl);
}


int main()
{
    PtrL ptrl;
    ptrl=(List *)malloc(sizeof(List));
    Intilize(ptrl, 1);
    Intilize(ptrl, 2);
    Intilize(ptrl, 3);
    Intilize(ptrl, 4);
    Intilize(ptrl, 5);
    Intilize(ptrl, 6);
    Intilize(ptrl, 7);
    // ShowList(ptrl);
    Del_All(ptrl);
}


下面为答案上的代码,运行了下 感觉不行:
void Del_All(PtrL ptrl)
{
    PtrL a,b,ma,mb;
    while(ptrl->next!=ptrl->next->next)
    {
        a=ptrl->next->next;b=ptrl->next;
        ma=ptrl->next;mb=ptrl;
        while(a!=ptrl->next)
        {
            if (a->Data<ma->Data)
            {
                ma=a;
                mb=b;
            }
            b=a;
            a=a->next;
        }
        print(a->Data);
        mb->next=ma->next;
        free(ma);
    }
    free(ptrl);
}

10、【王道算法】删除链表中重复的元素

void Del_Repeated(PtrL ptrl)
{
    PtrL a=ptrl->next,b; // 首先采用两个指针,一个用来指向比较点,也就是基准,如果别的节点跟它的值一样就要被删掉,另一个用来指向被比较点,如果跟前面a的值一样就要被惨遭删除的那种节点就是b来指着
    while (a->next) //  采用双层嵌套循环,一个是从头到尾确定基准点,另一个是确定了一点基准点后,对基准点后的节点进行扫描
    {  
        b=a;// 扫描不需要对前面进行,所以只需要对基准之后的扫描就ok
        while (b->next) // 用b->next作为判断条件是因为:如果确定下一点为空,那么说明现在所在的点就已经是最后一点了,至于下一点为空是删除了最后一点还是本身到了最后一点都可以兼容考虑。不需要额外的指令判断。
        {// 确定基准点后,就可以进行扫描,采用“站稳脚跟,向下一个节点眺望“的形式,因为这样可以方便的删除下一个符合删除要求的节点
            if (b->next->Data==a->Data)
                Del(b);  //如果符合要求,就删除,所以不需要 b=b->next;因为下一个节点已经是新扫描到的节点了,不能跳过。所以删除和跳过二选一  
            else
                b=b->next;  // 如果不是重复的,就直接跳过,确定下一点安全,跳过去。
        }
        a=a->next;
    } 
}


void print(int num)
{
    printf("n#########n#");
    printf("t%dt",num);
    printf("#n");
    printf("#########n");
}
void Del(PtrL ptrl)
{
    PtrL D;
    D=ptrl->next;
    ptrl->next=D->next;
    print(D->Data);
    free(D);
}

11、【王道算法】单链表中删除倒数第K个节点(答案是用双指针间隔一定来实现,我用递归)

//其实我还有另一种想法,用一个length函数确定链表长度,然后判定len-k 的正负来判定能否查找成功,能查找就直接查找第len-k个节点,不能的话返回0;
int length(PtrL ptrl)
{
    PtrL p=ptrl->next;
    int  count=0;
    while(p)
    {
        count++;
        p=p->next;
    }
    return count;
}


#include <stdio.h>
#include <stdlib.h>
//注:定义队列结构体及其指针
typedef struct List
{
    int Data;
    struct List *next;
} List, *PtrL;

void Intilize(PtrL ptrl,int data)
{
    PtrL s;
    s=(List *)malloc(sizeof(List));
    s->Data=data;
    s->next=ptrl->next;
    ptrl->next=s;
}


void print(int num)
{
    printf("n#########n#");
    printf("t%dt",num);
    printf("#n");
    printf("#########n");
}

void ShowList(PtrL ptrl)
{
    PtrL show;
    show=ptrl->next;
    while(show)
    {
        int num=show->Data;
        printf("n##########n#");
        printf("t%dt",num);
        printf("#n");
        printf("##########n");
        printf("t||nt||nt||nt||");
        printf("n   ~~~~~      " );
        printf("n    VVV       " );
        printf("n     V        " );
        printf("n" );
        show=show->next;
    }


}


void Fink_K(PtrL ptrl,int *k,int *n)
{
    if (ptrl->next)
    {
        Fink_K(ptrl->next,k,n);
    }
    (*n)++;
//妙处就在于用全局变量来界定循环次数,当从最后一个节点开始递增计数器n时,就可以等到n与k相等的那一刻,若不存在,就证明链表长度小于k
    if (*n==*k)
    {
        print(ptrl->Data);
    }   
}



int main()
{
    PtrL ptrl;
    ptrl=(List *)malloc(sizeof(List));
    Intilize(ptrl, 1);
    Intilize(ptrl, 2);
    Intilize(ptrl, 3);
    Intilize(ptrl, 4);
    Intilize(ptrl, 5);
    Intilize(ptrl, 6);
    Intilize(ptrl, 7);
    ShowList(ptrl);
    int *n,*k;
    int a=0,b=2;
    n=&a;k=&b;
    PtrL p=ptrl;
    Fink_K(p,k,n);
    if (a==b)
        printf("1");
    else 
        printf("0");
}

12、【王道算法】一些算法题目以及思想,暂时懒得实现了。麻烦,心累

  1. 一个单链表,只给你头指针,设计一个尽可能高效的算法查找倒数第k个节点,查找成功,返回data并且返回1,失败就返回0;
    1. 算法一:采用两个指针,并且一个指针先从头指针向前走k个节点,然后两个指针一起向前移动,直到前面的指针指向虚空;
    2. 算法二:采用递归方式,在递归代码后实现一个计数器n的自加操作,每次递归都检查k是否与n相等,相等即可返回data与1;不过要配合主函数内的全局指针
    3. 算法三:采用一个length函数确定链表长度,然后判定len-k 的正负来判定能否查找成功,能查找就直接查找第len-k个节点,不能的话返回0;
  2. 假设为Y型的双链表,但是两个链表长度不一定相等。
    1. 对另一个链表2进行遍历,如果链表2中某个节点的data与当前链表1指针所在节点的data相等
    2. 查看是否两个指针所指向的位置相等,即 if(a==b)
    3. 若不相等,则继续将链表2的指针向后移动,知道链表2结束;相等则可退出
    4. 移动链表1的指针,并且重复上面三步,直到链表1遍历结束
    5. 采用两次length函数,取得两个链表的长度之差k,然后用两个指针指向两个链表的头结点,较长的链表的指针向前移动k,之后两个指针同时移动,当指向的地址相同之时,也就是交叉点的位置。
    6. 【劣质算法】任取一个头结点,进行如下操作:
  3. 单链表保存m个整数且|data|<n,对链表中绝对值相等的节点只保留第一个,之后的统统删除。
  4. 牺牲空间换取时间,很绝妙的一个点子,写一段代码给大家看看(只要扫描一次)
1.     while(p->link!=NULL)
        2.     {
        3.         m=p->link->data>0?p->link->data:-p->link->data;
        4.         if(*(q+m)==0)
        5.         {
        6.             *(q+m)=1;
        7.             p=p->link;
        8.         }
        9.         else
        10.         {
        11.             r=p->link;
        12.             p->link=r->link;
        13.             free(r)
        14.         }
        15.     }
        16. //很妙的想法,那就是不对所有的值都进行操作,而是选择一个数组,也不是所有的空间都用上,如果链表这个节点的数值data作为码值的那个p[data]已经有过一次数(没有的时候等于0,有过一次就都等于1,并且删除节点的时候也不对其进行操作,所以不会变),那么就代表着这个数出现过,可以删除后面出现的!这是绝妙的一个应用!!!
  1. 蠢办法!在每个节点对在其后面的节点进行挨个的扫描,绝对值相等就干掉他!不相等就跳过!

正文之后

刚好图书馆管理员来赶人~~走也 ,回去做实时控制系统的作业,这周因为找老师面试所以作业都没做(PS:这是第二天更新的,mmp装了一晚上,修复引导,然后还特意装了个XP系统测试,结果还是GG,心痛,今天还得继续做)