链表简述


  链表无疑是数据结构中的基础,所以,有必要进行一些总结。链表分为单链表、双链表、循环链表等,在此,我们以单链表为例,简单了解一下链表的基本实现,至于双链表、循环链表等,实现可能比单链表要复杂,但基本原理一致。

链表的构造

  链表主要由节点构成,节点结构如下:

typedef struct Node
{
    int data;
    struct Node *next;
} NODE, *PNODE;

  data是实际存储数据,当然,我们也可以存储比int型更复杂的数据,next则是节点指针,每个节点的next指针指向下一个节点,从而形成了如下结构:
  在这里插入图片描述
  如果是循环链表,则是如下形式:
在这里插入图片描述

  这里面需要注意头节点,头节点并不保存有实际意义的数据,主要用来找到链表入口,方便链表操作。需要注意的是,虽然头节点没有有效数据,但确实不可缺少的,因为在删除操作时,如果没有头节点,而只使用一个指针指向第一个节点的话,一旦该节点删除,那么整个链表就会丢失。

链表的增删改查

/*
 * @Descripttion: 链表实现
 * @version: 
 * @Author: sueRimn
 * @Date: 2021-01-10 10:23:48
 * @LastEditors: sueRimn
 * @LastEditTime: 2021-01-10 17:14:01
 */
#include <malloc.h>
#include <iostream>

typedef struct NODE
{
    int data;
    struct NODE *next;
} Node, *pNode;

void Print_Link(pNode head)
{
    if (head == nullptr || head->next == nullptr)
    {
        printf("The link isempty!\n");
        return;
    }

    pNode p = head->next; //指向第一个节点
    while (p != nullptr)
    {
        printf("%d\n", p->data);
        p = p->next;
    }
}

/**
 * @name: 创建链表
 * @test: test font
 * @msg: 
 * @param {*}
 * @return {*}
 */
pNode CreateLink()
{
    pNode head = nullptr;
    head = (pNode)malloc(sizeof(Node));
    if (head == nullptr)
    {
        printf("malloc failed!");
        return head;
    }
    head->next = nullptr;

    return head;
}

/**
 * @name: 前插
 * @test: test font
 * @msg: 
 * @param {*}
 * @return {*}
 */
bool insert_head(pNode head, int value)
{
    if (head == nullptr)
        return false;

    pNode newNode = (pNode)malloc(sizeof(Node));
    if (newNode == nullptr)
        return false;

    newNode->data = value;
    newNode->next = head->next;
    head->next = newNode;

    return true;
}

/**
 * @name: 后插
 * @test: test font
 * @msg: 
 * @param {*}
 * @return {*}
 */
bool insert_tail(pNode head, int value)
{
    if (head == nullptr)
        return false;

    pNode p = head;
    while (p != nullptr && p->next != nullptr)
    {
        p = p->next;
    }
    //循环结束后找到最后一个节点

    pNode newNode = (pNode)malloc(sizeof(Node));
    if (newNode == nullptr)
    {
        printf("malloc error!\n");
        return false;
    }

    newNode->data = value;
    newNode->next = nullptr;

    p->next = newNode;

    return true;
}

/**
 * @name: 前删
 * @test: test font
 * @msg: 
 * @param {*}
 * @return {*}
 */
void delete_head(pNode head)
{
    if (head == nullptr || head->next == nullptr)
        return;

    pNode p = head->next; //要删除的节点
    head->next = p->next;

    free(p);
    p = nullptr;
}

/**
 * @name: 后删
 * @test: test font
 * @msg: 
 * @param {pNode} head
 * @return {*}
 */
void delete_tail(pNode head)
{
    if(head == nullptr || head->next == nullptr)
    return;

    pNode p = head;
    pNode prev = head; //用于记录最后一个节点前面的一个节点
    while (p != nullptr && p->next != nullptr)
    {
        prev = p;
        p = p->next;
    }
    //循环结束后找到最后一个节点

    prev->next = nullptr;
    
    free(p);
    p = nullptr;   
}

void clear_link(pNode head)
{
    if (head == nullptr || head->next == nullptr)
        return;

    while (head->next != nullptr)
    {
        delete_head(head);
    }
}

int main()
{
    pNode head = CreateLink();

    //前插
    for (int i = 1; i < 5; i++)
    {
        insert_head(head, i);
    }

    printf("insert head:\n");
    Print_Link(head);

    //前删
    delete_head(head);

    printf("delete head:\n");
    Print_Link(head);

    //清空
    clear_link(head);

    printf("clear link:\n");
    Print_Link(head);

    //后插
    for (int i = 1; i < 5; i++)
    {
        insert_tail(head, i);
    }

    printf("insert tail:\n");
    Print_Link(head);

    //后删
    delete_tail(head);

    printf("delete tail:\n");
    Print_Link(head);
}

  运行后,结果如下:

insert head:
4
3
2
1
delete head:
3
2
1
clear link:
The link isempty!
insert tail:
1
2
3
4
delete tail:
1
2
3

链表相关算法

链表反转

  链表反转示意图可以用下图表示:
  在这里插入图片描述

  总结如下:

  • 默认cur指向链表第一个节点,prev指向NULL;
  • 循环时,cur的next指针指向prev节点,prev节点指向cur节点,cur指针指向下一个节点。
  • 依次循环,最终链表彻底反转。

  代码实现如下:

/**
 1. @function: 链表反转
 2. @param {pNode} head
 3. @return {*}
 */
void reverse_Link(pNode head)
{
    if (head == nullptr || head->next == nullptr)
        return;

    pNode cur = head->next;
    pNode prev = nullptr;

    while (cur != nullptr)
    {
        pNode node = cur->next;
        cur->next = prev;
        prev = cur;
        cur = node;
    }

    //循环之后,prev成为第一个链表节点
    head->next = prev;
}

链表相交

  两个单链表,判断其是否相交,如果相交,获取交点。
  首先要明确,如果两个链表相交,那么,其一定是如下形式的:
在这里插入图片描述
  因为如果两链表如果相交于点D,由于链表通过next指针指向下一个节点,那么点D后的链表段两个链表是共有的。
  如果有相交的结点D的话,每条链的头结点先走完自己的链表长度,然后回头走另外的一条链表,那么两结点一定为相交于D点,因为这时每个头结点走的距离是一样的,都是 AD + BD + DC,而他们每次又都是前进1,所以距离相同,速度又相同,固然一定会在相同的时间走到相同的结点上,即D点。

void Node getIntersectionNode(Node headA, Node headB)
{
    if (headA == nullptr || headB == nullptr)
        return nullptr;
    Node p1 = headA;
    Node p2 = headB;
    while (p1 != p2)
    {
        if (p1 == nullptr)
            p1 = headB;
        else
            p1 = p1.next;

        if (p2 == nullptr)
            p2 = headA;
        else
            p2 = p2.next;
    }
    return p1;
}

获取链表中倒数第k个元素

在这里插入图片描述
  假定有total个节点,那么获取其倒数第k个节点的元素。
  可以这样分析:如果是倒数第k个节点,正数是第几个节点?由于一共有total个节点,倒数第k个节点的左侧那么一定有total-k个节点,那么倒数第k个节点正数也就是total-k+1个节点。即total-(k-1)。
  既然已经知道了是正数第total-(k-1),我们的当然可以直接遍历到第total-(k-1)个节点去,但是存在问题:链表我们只有其head节点,而事前并不知道其total是多少,除非我们先遍历一次,获取total数值,那么时间复杂度也就是O(2n),简化即O(n)。
  但这里介绍一种更简单的办法,只需遍历一次,即双指针法。
  首先定义两个指针prev、behind,则步骤如下:

  1. prev指针首先从head指针起,往前遍历k-1次。
  2. prev指针遍历k-1次后,behind指针指向head节点,并开始与prev指针一同遍历,直到prev指针的next为空。

  分析:当prev指针遍历k-1次后,也就是到达正数第k-1个节点,那么它还需要total-(k-1)次遍历才能到达第total个节点,如果behind指针和其一同遍历,则behind最终到达正数第total-(k-1)个节点,而正如前面分析,正数第total-(k-1)个节点,实际也就是倒数第k个节点。

/**
 * @function: 寻找倒数第k个节点
 * @param {pNode} head
 * @param {int} k
 * @return {int}
 */
int find_inverseK_node(pNode head, int k)
{
    if (head == nullptr || head->next == nullptr)
        return;

    pNode prev = head;
    pNode behind = head;
    int step = 0;
    while (prev->next != nullptr)
    {
        prev = prev->next;
        step++;

        if (step > (k - 1)) //prev先行k-1步,然后behind指针也开始运动
            behind++;
    }

    //k有可能大于了total,那么behind就没机会运动了
    if (behind == head)
        return -1;

    return behind->data;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值