数据结构-链表链表的相关算法

Posted Mount256

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构-链表链表的相关算法相关的知识,希望对你有一定的参考价值。

文章目录


(只是写了个大致思路,不具备可执行性)

1 删除元素

1.1 删除值为 x 的所有结点

1.1.1 不带头结点的单链表

  • 正常写法:
void Delete_X (LinkList &L, int x)
    LinkNode *p = L;
    LinkNode *prevNode; // 记录 p 的前驱结点
    LinkNode *nextNode; // 记录 p 的后继结点
    
    while (p != NULL)
        if (p->data == x)
            if (p == L) // 如果是头结点
                nextNode = p->next;
                free(p);
                L = nextNode;
            
            else // 如果是中间结点或尾结点
                nextNode = p->next;
                free(p);
                prevNode->next = nextNode;
            
        
        prevNode = p; // 记录 p 的前驱结点
        p = p->next;  // 遍历下一个结点 
    

  • 递归写法:
void Delete_X (LinkList &L, int x)
    LinkNode *p;
    
    if (L->data == x)
        p = L;
        L = L->next;
        free(p);
        Delete_X(L, x);
    
    else
        Delete_X(L->next, x);

1.1.2 带头结点的单链表

void Delete_X (LinkList &L, int x)
    LinkNode *p;
    LinkNode *prevNode; // 记录 p 的前驱结点
    LinkNode *nextNode; // 记录 p 的后继结点
    
    p = L->next;
    prevNode = L;
    while (p != NULL)
        if (p->data == x) 
            nextNode = p->next;
            free(p);
            prevNode->next = nextNode;
        
        prevNode = p; // 记录 p 的前驱结点
        p = p->next;  // 遍历下一个结点 
    

【注】欲删除值介于 s 和 t 之间的所有结点,需把条件改为(s <= p->data) && (p->data <= t)

1.2 删除重复元素

1.2.1 不带头结点的单链有序表

void Delete_Same (LinkList &L)
    LinkNode *p = L; // 初始指向链表头部
    LinkNode *nextNode; // 记录 p 的后继结点
    
    while (p != NULL)
        nextNode = p->next; // 记录 p 的后继结点
        if ((nextNode != NULL) && (nextNode->data == p->data)) // 发现重复元素
            p->next = nextNode->next;
            free(nextNode);
        
        else // 没有发现重复元素
            p = p->next;  // 遍历下一个结点 
    

1.2.2 带头结点的单链有序表

void Delete_Same (LinkList &L)
    LinkNode *p = L->next; // 初始指向链表第一个结点
    LinkNode *nextNode; // 记录 p 的后继结点
    
    while (p != NULL)
        nextNode = p->next; // 记录 p 的后继结点
        if ((nextNode != NULL) && (nextNode->data == p->data)) // 发现重复元素
            p->next = nextNode->next;
            free(nextNode);
        
        else // 没有发现重复元素
            p = p->next;  // 遍历下一个结点 
    

2 链表逆置

2.1 逆序输出结点

2.1.1 不带头结点的单链表

设 L 为不带头结点的单链表,编写算法实现从尾到头反向输出每个结点的值。

void Reverse_Print (LinkList &L)
    LinkNode *p = L; // 指向链表头部
    Stack S; // 定义栈
    int x;
    
    InitStack(S); // 初始化栈
    
    while (p != NULL)
        PushStack(S, p->data); // 入栈
        p = p->next;
    
    
    while (!EmptyStack(S))
        x = PopStack(S); // 出栈
        输出 x;
    

2.1.2 带头结点的单链表

设 L 为带头结点的单链表,编写算法实现从尾到头反向输出每个结点的值。

void Reverse_Print (LinkList &L)
    LinkNode *p = L->next;
    Stack S; // 定义栈
    int x;
    
    InitStack(S); // 初始化栈
    
    while (p != NULL)
        PushStack(S, p->data); // 入栈
        p = p->next;
    
    
    while (!EmptyStack(S))
        x = PopStack(S); // 出栈
        输出 x;
    

2.2 就地逆置

2.2.1 不带头结点的单链表

试编写一个算法将不带头结点的单链表就地逆置。

就地:空间复杂度为 O(1)。

思路:总是把第一个结点取下来,然后用尾插法重新建立链表。

LinkList Reverse (LinkList &L)
    LinkNode *head = L;         // 指向原链表头部
    LinkNode *nextNode;         // 保存原链表头部的指针域
    LinkNode *newHead = NULL;   // 指向新链表头部
    
    while (head != NULL)
        nextNode = head->next;  // 保存原链表头部的指针域
        head->next = newHead;   // 原链表头部的指针域指向新链表头部
        newHead = head;         // 新链表头部更新
        head = nextNode;        // 原链表头部更新
    
    
    L = newHead;
    return L;

2.2.2 带头结点的单链表

LinkList Reverse (LinkList &L)
    LinkNode *head = L->next;   // 指向原链表第一个结点
    LinkNode *nextNode;         // 保存原链表头部的指针域
    LinkNode *newHead = NULL;   // 指向新链表头部
    
    while (head != NULL)
        nextNode = head->next;  // 保存原链表头部的指针域
        head->next = newHead;   // 原链表头部的指针域指向新链表头部
        newHead = head;         // 新链表头指针更新
        head = nextNode;        // 原链表头指针更新
    
    
    L->next = newHead;          // 更新头结点指针域,头结点插到新链表头部
    return L;

3 链表有序

3.1 链表排序

3.1.1 不带头结点的单链表

有一个不带头结点的单链表 L,设计一个算法使其元素递增有序。

思路:每次取出原链表的第一个元素,与有序链表中每个元素进行对比,然后插入到有序链表的相应位置。

void Sort (LinkList &L)
    LinkNode *head = L;             // 原链表头指针
    LinkNode *nextNode;             // 保存原链表头部的指针域
    LinkNode *newHead;              // 有序链表头指针
    LinkNode *newNode, *newPrev;    // 有序链表结点指针及其前驱结点
    
    newHead = (LinkNode *) malloc(sizeof(LinkNode));
    newHead->data = L->data;
    newHead->next = NULL;
    
    while (head != NULL)
        newPrev = NULL;
        newNode = newHead;
        
        while ((newNode != NULL) && (head->data <= newNode->data)) // 有序链表指针指向第一个大于 head 元素的结点
            newPrev = newNode;       // 记录其前驱结点
            newNode = newNode->next; // 继续遍历新链表
                                    // 运行到此处,newPrev 在前,newNode 在后
        
        nextNode = head->next;  // 保存原链表头部的指针域
        if (newPrev == NULL)   // 如果要插入有序链表的头部位置
            head->next = newNode; 
            newHead = head;     // 更新有序链表头指针
        
        else                   // 如果要插入有序链表的中间和尾部位置
            newPrev->next = head;
            head->next = newNode;
        
        head = nextNode;        // 继续遍历原链表
    
    L = newHead;

3.1.2 带头结点的单链表

有一个带头结点的单链表 L,设计一个算法使其元素递增有序。

void Sort (LinkList &L)
    LinkNode *head = L->next;       // 原链表头指针
    LinkNode *nextNode;             // 保存原链表头部的指针域
    LinkNode *newNode, *newPrev;    // 有序链表结点指针及其前驱结点
    
    while (head != NULL)
        newPrev = L;                // 原链表头结点作为新链表的头结点
        newNode = L->next;
        
        while ((newNode != NULL) && (head->data <= newNode->data)) // 有序链表指针指向第一个大于 head 元素的结点
            newPrev = newNode;       // 记录其前驱结点
            newNode = newNode->next; // 继续遍历新链表
                                    // 运行到此处,newPrev 在前,newNode 在后
        
        nextNode = head->next;  // 保存原链表头部的指针域
        newPrev->next = head;
        head->next = newNode;
        head = nextNode;        // 继续遍历原链表
    

3.2 链表有序输出

3.2.1 不带头结点的单链表

按递增次序输出单链表各结点的数据元素,每输出一个将该结点删除。

思路:每次遍历链表找出最小元素,输出该元素,然后删除。

void Print_Min (LinkList &L)
    LinkNode *prev; // 扫描指针
    LinkNode *prevNode, *nodeMin = L, *nextNode;  // 最小值结点(初始指向链表第一个结点)以及其前驱和后继结点
    int min = L->data;
    
    while (L != NULL)
        prev = L;
        while (prev->next != NULL)
            if (prev->next->data < min)
                prevNode = prev;            // 记录最小值的前驱结点
                nodeMin = prev->next;       // 记录最小值结点
                min = nodeMin->data;  
                nextNode = prev->next->next;// 记录最小值的后继结点
            
            prev = prev->next;
        
        输出 min;
        
        if (nodeMin == L) // 如果第一个结点为最小值
            free(nodeMin);
            L = nextNode;
        
        else
            free(nodeMin);
            prevNode->next = nextNode;
        
    

3.2.2 带头结点的单链表

按递增次序输出单链表各结点的数据元素,每输出一个将该结点删除。

思路:每次遍历链表找出最小元素,输出该元素,然后删除。

void Print_Min (LinkList &L)
    LinkNode *prev; // 扫描指针
    LinkNode *nodeMin = L->next, *prevNode, *nextNode;  // 最小值结点(初始指向链表第一个结点)以及其前驱和后继结点
    int min = L->next->data;
    
    while (L->next != NULL) // 循环到只剩头结点
        prev = L;
        while (prev->next != NULL)
            if (prev->next->data < min)
                prevNode = prev;            // 记录最小值的前驱结点
                nodeMin = prev->next;       // 记录最小值结点
                min = nodeMin->data;
                nextNode = prev->next->next;// 记录最小值的后继结点
            
            prev = prev->next;
        
        
        输出 min;
        free(nodeMin); // 删除最小值结点
        prevNode->next = nextNode; 
    
    
    free(L);

4 合并链表

4.1 两个递增链表合并为一个递增链表

4.1.1 不带头结点的单链表

思路:设置双指针,建立新链表,运用尾插法,把两个链表的结点按升序插入即可。

LinkList Merge (LinkList &A, LinkList &B)
    LinkNode *pa = A, *pb = B; // 扫描指针,指向前驱结点
    LinkNode *prevNode, *node, *nextNode; // 记录前驱结点、当前要插入的结点、后继结点
    LinkNode *C, *newNode;    // 新链表
    LinkNode *tail; // 新链表的尾指针
    
    if (A->data < B->data) // 单独处理两个链表的第一个结点,这两个结点单独保留,将问题转化为“带头结点的单链表”
        newNode = (LinkNode *) malloc(sizeof(LinkNode)); // 链表 C 的第一个结点
        newNode->data = A->data;
        C = newNode;
        newNode = (LinkNode *) malloc(sizeof(LinkNode)); // 链表 C 的第二个结点
        newNode->data = B->data;
        newNode->next = NULL;
        C->next = newNode;
        tail = newNode;
    
    else
        newNode = (LinkNode *) malloc(sizeof(LinkNode)); // 链表 C 的第一个结点
        newNode->data = B->data;
        C = newNode;
        newNode = (LinkNode *) malloc(sizeof(LinkNode)); // 链表 C 的第二个结点
        newNode->data = A->data;
        newNode->next = NULL;
        C->next = newNode;
        tail = newNode;
    

    while ((pa->next != NULL) && (pb->next != NULL))
        if (pb->next->data < pa->next->data)
            prevNode = pb;          // 记录前驱结点···(1)
            node = pb->next;        // 记录当前要插入的结点
            nextNode = node->next;  // 记录后继结点
            prevNode->next = nextNode; // 前驱结点指向后继结点···(2)
            tail->next = node;      // 往新链表尾插当前结点···(3)
            tail = node;            // 更新新链表尾指针
            pb = pb->next;          // 链表 B 的扫描指针继续遍历···(4)
        
        else
            prevNode = pa;          // 记录前驱结点···(1)
            node = pa->next;        // 记录当前要插入的结点
            nextNode = node->next;  // 记录后继结点
            prevNode->next = nextNode;  // 前驱结点指向后继结点···(2)
            tail->next = node;      // 往新链表尾插当前结点···(3)
            tail = node;            // 更新新链表尾指针
            pa = pa->next;          // 链表 A 的扫描指针继续遍历···(4)
        
    
    
    // 运行到此处,一定有且仅有一个链表非空
    if (A->next != NULL)    // 若链表 A 还未空
        tail->next = A->next; // 新链表尾部直接插入链表 A 的剩余部分
    if (B->next != NULL)    // 若链表 B 还未空
        tail->next = B->next; // 新链表尾部直接插入链表 B 的剩余部分
        
    free(A); // 删除链表 A
    free(B); // 删除链表 B
    return C;

4.1.2 带头结点的单链表

思路:设置双指针,建立新链表,运用尾插法,把两个链表的结点按升序插入即可。

LinkList Merge (LinkList &A, LinkList &B)
    LinkNode *pa = A, *pb = B; // 扫描指针,指向前驱结点
    LinkNode *prevNode, *node, *nextNode; // 记录前驱结点、当前要插入的结点、后继结点
    LinkNode *C = (LinkNode *) malloc(sizeof(LinkNode)); // 新链表的头结点
    LinkNode *tail = C; // 新链表的尾指针

    while ((pa->next != NULL) && (pb->next != NULL))
        if (pb->next->data < pa->next->data)
            prevNode = pb;          // 记录前驱结点···(1)
            node = pb->next;        // 记录当前要插入的结点
            nextNode = node->next;  // 记录后继结点
            prevNode->next = nextNode; // 前驱结点指向后继结点···(2)
            tail->next = node;      // 往新链表尾插当前结点···(3)
            tail = node;            // 更新新链表尾指针
            pb = pb->next;          // 链表 B 的扫描指针继续遍历···(4)
        
        else
            prevNode = pa;          // 记录前驱结点···(1)
            node = pa->next;        // 记录当前要插入的结点
            nextNode = node->next;  // 记录后继结点
            prevNode->next = nextNode;  // 前驱结点指向后继结点···(2)
            tail->next = node;      // 往新链表尾插当前结点···(3)
            tail = node;            // 更新新链表尾指针
            pa = pa->next;          // 链表 A 的扫描指针继续遍历···(4)
        
    
    
    // 运行到此处,一定有且仅有一个链表非空
    if (A->next != NULL)    // 若链表 A 还未空
        tail->next = A链表

算法&数据结构

算法&数据结构

算法&数据结构

算法&数据结构

算法链表链表相关问题总结