数据结构(七)——链表描述线性表练习

线性表的链式表示,来自王道,部分思路与答案不同。
如有错误,欢迎指正!
/*
结构体声明
typedef struct LNode{
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;

*/



// 1. 设计一个递归算法,删除不带头结点的单链表L中的所有值为x的结点
// 算法思想:传递的为上一结点的指针域,挺有意思的一个算法
// 时间复杂度:O(n),空间复杂度O(n)
void deleteAllx(LinkList &L, ElemType x)
{
    LNode* p;
   if(L == null)
    return;
   if(L->data == x)
   {
       p = L;
       L = L->next; //为上一结点的指针域
       free(p); // C++ STL 用于释放malloc函数所开辟的空间,与malloc函数配对使用
       deleteAllx(L, x);
   }
   else
    deleteAllx(L->next, x);

}

//2. 在带头节点的单链表L中,删除所有值为x的结点,并释放其空间,假设值为x的结点不唯一
// 算法思想: 两指针,一指针记录前结点,一指针记录当前结点
//时间复杂度:O(n),空间复杂度O(1)

void deleteAllx2(LinkList &L, ElemType x)
{
    LNode* p1 = L;
    LNode* p2 = L->next;
    LNode* tem;
    while(p2!= null)
    {
        if(p2->data == x)
        {
            tem = p2;
            p1->next = p1->next->next;
            p2 = p1->next;
            free(tem);
        }else
        {
            p2 = p2->next;
            p1 = p1->next;
        }

    }
}

// 3. 设L为带头节点的单链表,编写算法实现从尾到头反向输出每个结点的值
//算法思想: 第一想法是正向输出取反,但这样空间复杂度为O(n)
//参考答案:利用递归栈,妙!虽然空间复杂度还是O(n),但是代码简单,时间复杂度O(n)
void reverseOutput(LinkList &L)
{
    if(L->next == null)
    {
        cout << L->data << " ";
        return;
    }
    if(L->next != null)
    {
        reverseOutput(L->next);
        cout << L->data <<" ";
        return;
    }
}

// 4. 在带头结点的单链表L中删除一个最小值结点的高效算法(假设最小值结点唯一)
// 算法思想:提供一个指针记录最小值结点的前驱结点,遍历(逃不过遍历)
// 时间复杂度:O(n) 空间复杂度:O(1)

void deleteMin(LinkList &L)
{
    LNode* p1 = L;// 记录前驱结点
    LNode* p2 = L->next;// 记录目前结点
    LNode* tem;
    if(p2 == null)
        return;

    ElemType x = p2->data;// 记录当前最小值
    while(p2->next != null)
    {
        if(p2->next->data < x)
        {
            p1 = p2;
            p2 = p2->next;
            x = p2->data;
        }else{
            p2 = p2->next;
        }
    }
    tem = p1->next;
    p1->next = p1->next->next;
    free(tem);
    return;
}

// 试编写算法将带头结点的单链表就地逆置,即辅助空间复杂度为O(1)
// 算法思想:提供一指针指向当前结点,遍历链表;再提供一指针指向下一结点
//           从第一个结点开始,下一结点的next指向前结点。
//           两结点各自往前移动一个结点,直到遍历结束
// 时间复杂度:O(n),空间复杂度O(1)

void reverseLink(LinkList &L)
{
    LNode* p1 = L;
    LNode* p2 = L->next;
    LNode* p3;

    L->next = null;// 断开,只留一个结点

    if(p2 == null)
        return;

    if(p2->next == null) // 只有两个结点
    {
        p2->next = p1;
        return;
    }
    while(p2->next != null) // 有两个以上结点
    {
        p3 = p2->next;
        p2->next = p1;
        p1 = p2;
        p2 = p3;
    }
    return;
}

// 有一个带头结点的单链表L,设计一个算法使其元素递增有序
// 算法思想:是哪个大聪明想用链表来排序呀
/*
    如果用选择排序,需要提供一指针记录最小值的前驱结点,再提供一指针
    保存交换位置,再提供一指针记录头结点,时间复杂度为O(n^2)

    如果使用冒泡排序,同样需要三个指针,两个当前比较节点的指针,一个前结点
    的指针或者前节点next域指针,这样操作会简单一点,时间复杂度为O(n^2)

    参考答案给出的方法是插入排序,时间复杂度仍然为O(n^2),同样利用了三个指针。
    当然可以使用快排,但我还没掌握,这里使用插入排序
*/

void Sort(LineList &L)
{
    LNode* p = L->next; //1
    LNode* pre;
    LNode* r = p->next; //2

    p->next = null;// 只保留头结点和第一个结点,断掉了最初的链表
    p = r;// 从第二个结点开始 2

    while(p != null)
    {
        r = p->next;// 3
        pre = L;// head
        while(pre->next != null && pre->next->data < p->data)
        {
            pre = pre->next;
        }
        p->next = pre->next;
        pre->next = p;
        p = r;
    }

}
/**
设在一个带表头结点的单链表中所有元素结点的数据值无序,试编写一个函数,删除表中
所有介于给定的两个值(作为函数参数给出)之间的元素的元素(若存在)
时间复杂度:O(n),空间复杂度:O(1)
算法思想:
    遍历,遇见就删除
**/

void deleteBetween(LinkList &L, int m ,int n)
{
    LNode* p1 = L;
    LNode* p2 = L->next;
    LNode* tem;
    if(p2 == null)
        return;
    while(p2 != null)
    {
        if(p2->data>m && p2->data<n)
        {
            tem = p2;
            p1->next = p1->next->next;
            delete tem;
            p2 = p1->next;
        }
    }

}
/**
给定两个单链表,编写算法找出两个链表的公共结点

算法思想:这个有意思,有时间复杂度为O(m+n)的解法,
利用了公共结点之后的结点必定相同,需要辅助函数判断
链表的长度

时间复杂度:O(m+n) 空间复杂度:O(1)
**/

int getLength(LinkList L1)
{
    int c = 0;
    while(L1!=null)
    {
        c++;
        L1 = L1->next;
    }
    return c;
}

LinkList findCommon(LinkList L1, LinkList L2)
{
    int len1 = getLength(L1);
    int len2 = getLength(L2);
    int dist;
    LinkList longL,shortL;
    if(len1 > len2)
    {
        longL = L1;
        shortL = L2;
        dist = len1 - len2;
    }else{
        longL = L2;
        shortL = L1;
        dist = len2 - len1;
    }
    while(dist--)
        longL = longL->next;
    while(longL != null)
    {
        if(longL == shortL)
            return longL;
        else
        {
            longL = longL->next;
            shortL = shortL->next;
        }
    }
    return null;
}

/**
    给定一个带头结点的单链表,按递增次序输出单链表中各数据元素
    并释放结点所占的存储空间

    算法思想:上面使元素递增有序的算法加个输出,最后删除

    时间复杂度:O(n^2)空间复杂度,O(1)
**/

void deleteandSortedOutput(LinkList &L, ostream &out)
{
    Sort(L);
    LNode* tem;
    LNode* p1 = L->next;
    while(p1!=null)
    {
        out << L->data <<" ";
        tem = p1;
        p1 = p1->next;
        delete tem;
    }
    delete L;
}
/**
   将一个带头结点的单链表A分解为两个带头结点的单链表A和B,使得A表中含有
   表中序号为奇数的元素,而B表中含有原表中序号为偶数的元素,且保持其相对
   顺序不变。
   算法思想:创建头结点+删除结点+保留被删除的节点并连接到新链表
   时间复杂度:O(n) 空间复杂度:O(1)

**/
LinkList oddAndEven(LinkList &A)
{
    LinkList B = (LinkList)malloc(sizeof(LNode));
    B->next = null;
    LNode* pre = A;
    LNode* p1 = A->next;
    LNode* p2 = B;
    int c = 1;
    while(p1 != null)
    {
        if(c%2 != 0)
        {
            pre = p1;
            p1 = p1->next;
            c++;
        }else
        {

            p2->next = p1;// B链表与偶数节点相连
            p2 = p2->next;

            pre->next = p1->next;// A链表的偶数节点的前驱指向偶数节点的后驱
            p1 = p1->next;// p1节点前移
            p2->next = null;// A链表该偶数节点的next域断掉
            c++;
        }
    }
    return B;
}

/**
    C={a1,b1,a2,b2,..,an,bn}拆分成两个不同的链表A = {a1,a2,a3,..,an}和
    B={bn,bn-1,....,b2,b1}
    算法思想:和上一题的不同是B上一题使用尾插,这里使用头插
             刚看到题的思路是直接逆转链表B
    时间复杂度:O(n) 空间复杂度:O(1)
**/
LinkList oddAndReverseEven(LinkList &A)
{
    LinkList B = (LinkList)malloc(sizeof(LNode));
    B->next = null;
    LNode* pre = A;
    LNode* p1 = A->next;
    LNode* p3;
    int c = 1;
    while(p1 != null)
    {
        if(c%2 != 0)
        {
            pre = p1;
            p1 = p1->next;
            c++;
        }else
        {

            B->next = p1;// B链表与偶数节点相连

            pre->next = p1->next;// A链表的偶数节点的前驱指向偶数节点的后驱
            p1 = p1->next;// p1节点前移

            B->next->next = p3;//头插
            p3 = B->next
            c++;
        }
    }
    return B;
}
/**
    假设有两个按元素值递增次序排列的线性表,均以单链表形式存储。请编写
    算法将这两个单链表归并为一个按元素值递减次序排列的单链表,并要求利用
    原来两个单链表的结点存放归并的单链表
    算法思想:
        有序链表的合并
        定义一不放数据的头结点,最后删除
        每个链表定义一个遍历节点,一个遍历节点的前驱节点
        就和公式化线性表的合并流程一样开始合并
    时间复杂度:O(n) 空间复杂度O(1)
**/

LinkList mergeLinkList(LinkList &L1, LinkList &L2)
{
    LinkList head = (LinkList)malloc(sizeof(LNode));
    LNode* r = head;
    LNode* p1 = L1;
    LNode* p2 = L2;
    LNode* temp;

    while(p1 != null && p2 != null)
    {
        if(p1->data > p2->data)
        {
            r->next = p2;// 新链与旧链链接
            r = r->next;// 新链缝合节点往前一节点
            p2 = p2->next;// 旧链遍历节点往前一节点
            r->next = null;// 新链最后一节点的next域为null

        }else
        {
            r->next = p1;
            r = r->next;
            p1 = p1->next;
            r->next = null;

        }
    }
    if(p1 != null)  // 把最后一段没有比较过的连上
        r->next = p1;
    else
        r->next = p2;

    temp = head; // 删除辅助用的头结点
    head = head->next;
    delete temp;

    return head;
}

/**
    设A和B是两个单链表(带头结点),其中元素递增有序。设计一个算法从A和B中的公共
    元素产生单链表C,要求不破坏A、B的节点
    算法思想: 重复创建新节点,取元素直接赋值
               和合并流程基本一样,不一样的是我们这次只取data相等的节点
    时间复杂度:O(n) 空间复杂度:O(1)
**/
LinkList mergeLinkListData(LinkList &L1, LinkList &L2)
{
    LNode* head = (LinkList)malloc(sizeof(LNode));
    LNode* r = head;
    LNode* p1 = L1->next;
    LNode* p2 = L2->next;
    while(p1 != null && p2 != null )
    {
        if(p1->data < p2->data)
        {
            p1 = p1->next;
        }else if(p1->data > p2->data)
        {
            p2 = p2->next;
        }else
        {
            LNode* temp = (LinkList)malloc(sizeof(LNode));
            temp->data = p1->data;
            r->next = temp;
            r = r->next;
            p1 = p1->next;
            p2 = p2->next;
        }

    }
    return head;
}

/**
    已知两个链表A和B分别表示两个集合,其元素递增排序。编制函数,求A和B
    的交集,并存放于A链表中
    算法思想:交集和公共元素不是一个意思吗??? 区别就是一个是链表C,一个是
              原链表A,不用申请新空间,直接在A里delete
    时间复杂度:O(n) 空间复杂度:O(1)

**/

void mergeLinkList2(LinkList &L1, LinkList &L2)
{
    LNode* p1 = L1;
    LNode* pre = (LinkList)malloc(sizeof(LNode));
    pre->next = p1;
    LNode* p2 = L2;

    while(p1 != null && p2 != null)
    {
        if(p1->data < p2->data)
        {
            pre->next = pre->next->next;
            pre = p1;
            delete p1;
            p1 = pre->next;

        }else if(p1->data > p2->data)
        {
            p2 = p2->next;

        }else
        {
            pre = p1;
            p1 = p1->next;
        }
    }
}

/**
    两个整数序列A = a1,a2,a3,..,an和B = b1,b2,b3,,=..,bn已经存入两个
    单链表中,设计一个算法,判断序列B是不是序列A的连续子序列
    算法思想:如果n>m,返回false
              取出B中的b1,与序列A逐个比对,若存在ak = b1 (k <= m-n+1)
              且ak的后续元素与B相同,返回true,否则返回false
    时间复杂度:O(n),n为序列A长度 空间复杂度:O(1)
**/
bool contiSequence(LinkList &L1, LinkList &L2)
{
    int m = getLength(L1);
    int n = getLength(L2);
    LNode* p1 = L1;
    LNode* p2 = L2;
    if(m > n)
        return false;
    for(int i = 0; i<=m-n; i++)
    {
        if(p1->data != p2->data)
        {
            p1 = p1->next;
        }
        if(p1->data == p2->data)
        {
            for(int j = 1; j<n; j++)
            {
                p1 = p1->next;
                p2 = p2->next;
                if(p1->data != p2->data)
                {
                    return false;
                }
            }
            return true;
        }
    }
    return false;
}
/**
设计一个算法用于判断带头结点的循环双链表是否对称

设计思想:
第一反应是用栈比较好,话说奇数个节点算不算对称
不过既然在链表这一章应该有链表可以和栈相媲美的算法。
提供头尾两个指针,因为是循环双链表,所以找到尾指针的
时间复杂度为O(1)。头指针往右,尾指针往左;当指针的值
每次都相等且遍历至两指针指向的节点相同(奇数)或next
域指向另一指针时返回true

时间复杂度:O(n) 空间复杂度:O(1)
**/

bool isSymmetry(LinkList &L)
{
    LNode* head = L->next;
    LNode* tail = L->prior;

    while(head != tail && head->next != tail)
    {
       if(head->data == tail->data)
       {
           head = head->next;
           tail = tail->prior;
       }else{
           return false;
       }
    }
    return true;
}

/**
    有两个循环单链表,链表头指针分别为h1和h2,编写一个函数
    将链表h2链接到链表h1后,要求链接后的链表仍保持循环链表
    形式

    算法思想:首先找到两链表的尾指针,之后连接

    时间复杂度:O(n) 空间复杂度:O(1)
**/

void connectLinkList(LinkList &L1, LinkList &L2)
{
    LNode* p1 = L1;
    LNode* p2 = L2;
    LNode* t1;
    LNode* t2;
    while(p1->next != L1)
    {
        p1 = p1->next;
    }
    t1 = p1;

     while(p2->next != L2)
    {
        p2 = p2->next;
    }
    t2 = p2;

    p1 = L1;
    p2 = L2;

    t1->next = p2;
    t2->next = p1;
}

/**
设有一个带头节点的循环单链表,其节点值均为正整数。设计一个算法,反复
找出单链中最小的元素并输出,然后将该节点从中删除,直到但链表为空为止
,再删除表头节点
算法思想:提供一个指针,从头节点开始反复遍历,再提供一个指针保存最小
值,每当回到头指针时,删除最小值节点,直到链表只剩头结点,删除头节点
时间复杂度:O(n^2) 空间复杂度:O(1)
**/
void outputMin(LinkList &L)
{
    LNode* p1 = L->next;
    LNode* m = L->next;
    LNode* pre = L;
    LNode* pre2 = L;
    while(L->next != null)
    {
        while(p1 != L)
        {
            if(p1->data < m->data)
            {
                pre2 = pre;
                m = p1;
            }
            pre = p1;
            p1 = p1->next;
        }
        cout << m->data << " ";
        pre2->next = pre2->next->next;
        delete m;
    }
    delete L;
}
/**
设头指针为L的带有表头结点的非循环双链表,其每个节点还有一个
访问频度域freq。在链表被启用前,初始化为0.每当在链表中进行一
次Locate(L, x)运算时,令元素值为x的结点中的freq域的值增1,并
使此链表中结点保持按访问频度递减的顺序排列,同时最新访问的结点
排在频度相同的结点前面,以便使频繁访问的结点总是靠近表头。试编写
符合上述要求的Locate(L,x)运算的算法,该运算为函数过程,返回找到节点
的地址,类型为指针类型

算法思想:以链表已经排好顺序为前提,链表中的一元素值为x的freq域的值增
1便要调整结点顺序使之按频度递减排序,且相同频域中该结点在最前面。因为
返回值为节点的地址,故应该不考虑多个节点元素值都为x的情况.
涉及双链表的插入和删除操作。

时间复杂度:  空间复杂度:O(1)
**/

LinkList Locate(LinkList &L, ElemType x)
{
    LNode* p1 = L->next;
    LNode* p2;
    LNode* p3;

    while(p1->data != x && p1 != null)
    {
        p1 = p1->next;
    }
    if(p1 == null)
        throw "no element's data equals x.";

    p1->freq = p1->freq + 1;
    p2 =p1;
    while(p2->freq->data != p1->freq + 1)
    {
        p2 = p2->next;
    }
    p3 = p2->next;
    // 到现在p1是要寻找的元素,p2是p1要插入位置的前驱元素,p3是要插入位置的后驱元素。
    p1->pred->next = p1->next;// p1的前驱和后驱相互链接
    p1->next->pred = p1->pred;
    p2->next = p1;// p1的前驱与p1连接
    p1->pred = p2;
    p1->next = p3;// p1的后驱与p1连接
    p3->pred = p1;

    return p1;
}

/**
已知一个带表头结点的单链表,在不改变链表的前提下,请设计一个
尽可能高效的算法,查找链表中倒数第K个位置上的节点(k为正整数)
。若查找成功,输出该节点的数据值,否则,只返回0.

算法思想:我只想到了遍历两遍的算法,参考答案的思路为提供两个指针,
一个指针移动k个位置,然后两指针同时移动,当前面的指针移动到尾部时
,后一指针位置即是我们要找的节点,只需要遍历一遍。

时间复杂度:只遍历一遍,空间复杂度:O(1)
**/

bool findk(LinkList &L, int k)
{
    LNode* p1 = L;
    LNode* p2 = L;
    int c;
    for(c = 0; c <= k; c++)
    {
        p1 = p1->next;
        if(p1 == null)
            return false;
    }
    while(p1->next != null)
    {
        p1 = p1->next;
        p2 = p2->next;
    }
    cout << p2->data <<" ";
    return true;
}

/**
找出两个不等长带头结点单链表的共同后缀(即节点的位置相同)
和第八个太像了。速通一遍,当做复习

算法思想:先对齐尾部,同时遍历,当遍历到同一节点便是后缀起始位置

时间复杂度:O(m+n)空间复杂度:O(1)
**/
LinkList commonSuffix(LinkList &L1, LinkList &L2)
{
    int m = getLength(L1);
    int n = getLength(L2);
    LNode* shortL;
    LNode* longL;
    int dis;
    if(m > n)
    {
        dis = m-n;
        shortL = L2;
        longL = L1;
    }else
    {
         dis = n-m;
         shortL = L1;
        longL = L2;
    }
    while(dis--)
        longL = longL->next;

    while(longL->data != shortL->data && shortL != null)
    {
        longL = longL->next;
        shortL = shortL->next;
    }
    return shortL;
}

/**
单链表保存m个整数,对于给定整数x,删除所有数据值为x或(-x)的结点

算法思想:和第二题很像,速通速通

时间复杂度:O(n)空间复杂度:O(1)
**/

void deleteXAndAbsx(LinkList &L, int x)
{
    int ax = -x;
    LNode* pre = L;
    LNode* p = L->next;

    while(p != null)
    {
        if(p->data == x || p->data == ax)
        {
            pre->next = pre->next->next;
            delete p;
            p = pre->next;
        }else
        {
            pre = p;
            p = p->next;
        }
    }
}
/**
设计一个算法完成以下功能,判断一个链表是否有环,如果有,找出环的入口点
并返回,否则返回NULL

算法思想:我想到的就是指针的指针,估计很难想到参考答案的解法
设置一个快指针和慢指针,慢指针每次走一步,快指针每次走两步。如果有环,慢指针
一定比快指针晚进入环,并在若干步后两指针在环上重合,可以判断一个链表是否有环。

之后是环的入口的问题,设头结点到入口的距离为a,相遇点到入口的距离为x,环长为r
有等式2(a+x) = a+n*r+x成立

等式左边是快指针的“路程”,右边是慢指针的“路程”。相遇时等式成立,
化简则有a+x = n*r;即a+x正好等于在入口处的结点旋转n圈回到入口处。

如果头结点和入口处的节点同时移动,一次走一步,走a+x步,则会在入口处相遇。
但是我们不知道入口处的节点在哪

所以换一种形式,我们现在知道相遇点x,相当于入口处的节点先走了x步。头节点和
相遇节点同时走a步,便可在入口处相遇。

时间复杂度:O(n) 空间复杂度:O(1)
**/

LinkList FindLoopStart(LinkList &L)
{
    LNode* fast = L;
    LNode* slow = L;
    LNode* p1 = L;
    LNode* p2;
    while(fast != null)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
            break;
    }
    if(fast == null)
        return null;
    p2 = slow;
    while(p1 != p2)
    {
        p1 = p1->next;
        p2 = p2->next;
    }
    return p1;
}
/**
设线性表L = (a1,a2,a3,...,an-2,an-1,an)采用带头结点的单链表保存,请设计
一个空间复杂度为O(1)且时间上尽可能高效的算法,重新排列L中的各节点
得到线性表L` = (a1,an,a2,an-1,a3,an-2,...)

算法思想:
最初想法:
提供一个尾节点和头结点指针,头结点指针往右遍历,尾节点指针往左
遍历,同时删除尾节点,插入到要求的位置。
当头结点指针的next域为尾节点指针时,算法完成。

但是这不是双向链表,无法从后往前读,所以应该翻转后半段元素指针指向。

时间复杂度:O(n) 空间复杂度:O(1)
**/

void transformList(LinkList &L)
{
    int n = getLength(L);
    int m = n/2+1;
    LNode* p1 = L->next;
    LNode* p2 = L->next;
    LNode* tail;
    LNode* p3;

    while(m--)// 找逆置那一部分的头结点
    {
        p1 = p1->next;
    }
    reverseLink(p1); // 之前写的逆置链表方法

    while(tail->next != null)// 寻找尾节点
    {
        tail = tail->next;
    }


    while(p2->next != tail)
    {
        p3 = tail;
        tail = tail->next; // 注意此时是往左移动

        p3->next = p2->next;
        p2->next = p3;
        p2 = p3->next;
    }
}






猜你喜欢

转载自blog.csdn.net/qq_41882686/article/details/106889835
今日推荐