【数据结构】线性表:顺序表、单链表

本文详细介绍了链表的基本概念、不同类型的链表(如顺序表、单链表、循环链表、双向链表)及其操作方法,并提供了多种应用场景的具体实现案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1,顺序表

逻辑上相邻的两个元素物理位置上也相邻。
可以随机读取,但增删操作复杂。

2,单链表

  1. 增删快、查询慢

1)定义

结点:

class ListNode{
        int val;
        ListNode next;
    }

2)头插

    public void insertAtHead(int val) {
        ListNode newNode = new ListNode(val);
        newNode.next = head;
        head = newNode;
    }

3)尾插

    public void insertAtTail(int val) {
        //1个新结点(临时结点)
        ListNode newNode = new ListNode(val);
        if (head == null) {
            // 若链表为空,新节点就是头节点
            head = newNode;
        } else {
        		//一个指针cur作为当前遍历结点的指针
            ListNode current = head;
            // 遍历链表,找到尾节点
            while (current.next != null) {
                current = current.next;
            }
            // 将新节点插入到尾节点之后
            current.next = newNode;
        }
    }

4)链表反转

假设链表为 1→2→3→∅,我们想要把它改成 ∅←1←2←3。

public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        //旧的链表头指针head 头删,cur作为遍历结点,需要断开cur后的指针;断开后为避免丢失,需要一个temp为临时存储
        //新的链表newhead 头插,此时newhead->null
        ListNode newHead = null, cur = head, temp = null;
        while (cur != null) {
            //1 遍历下一个,头出
            temp = cur.next;
            //2 断开旧指针,指向新指针
            cur.next = newHead;
            //3 头插法 指向新的头
            newHead = cur;
            //4 准备下一个结点
            cur = temp;
        }
        return newHead;
    }
  1. 从尾到头打印链表
    输入一个链表,从尾到头打印链表每个节点的值。
    解题思路:由于打印是只读操作,不宜改变原数据格式。可以借助辅助栈,或者使用递归实现(注意链表太长会导致函数调用层级变深,溢出)。

5)删除

总的复杂度:[(n-1)*O(1) +O(n)]/n = O(1)

1>头删

O(1),直接头指针后移

public void deleteAtHead(ListNode head) {
        if (head == null) {
            // 链表为空,无需删除
            return;
        }
        // 将 head 指针指向下一个节点
        head = head.next;
    }

2>尾删

O(n),先遍历再删除

public void deleteAtTail(ListNode head) {
        if (head == null) {
            // 链表为空,无需删除
            return;
        }
        if (head.next == null) {
            // 链表只有一个节点,将头指针置为 null
            head = null;
            return;
        }
        ListNode prev = null;
        ListNode current = head;
        // 找到倒数第二个节点
        while (current.next != null) {
            prev = current;
            current = current.next;
        }
        // 删除尾节点
        prev.next = null;
    }

6)链表中倒数第k个结点

输入一个链表,输出该链表中倒数第k个结点。
思路:定义一快一慢两个指针,快指针走K步,然后慢指针开始走,快指针到尾时,慢指针就找到了倒数第K个节点。
类似题目:

  1. 找中间节点:
    使用两个指针,同时从头结点出发,一个走一步,一个走两步。
    当走的快的结点到链表尾部时,走得慢的指针正好在链表中间。
    如果链表中结点为奇数个,返回中间结点;如果是偶数,返回中间结点中的任意一个。
  2. 判断一个单向链表是否形成了环形结构:
    使用两个指针,同时从头结点出发,一个走一步,一个走两步。
    如果走的快的指针追上了走得慢的指针,那么就是环形链表。
    如果走的快的指针走到链表的末尾都没有追上第一个指针,那么就不是环形。
public boolean isPalindrome(ListNode head) {
        //快慢指针 first一次走2步  second一次走1步  first如果追上second表示回文
        if (head == null || head.next == null) {
            return Boolean.FALSE;
        }
        ListNode slow = head, fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            //快追上慢指针 说明有环
            if (fast == slow) {
                return Boolean.TRUE;
            }
        }
        return Boolean.FALSE;
    }

7)链表合并

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
思路:判断两个头结点的值,用较小的作为新链表的头结点。

    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        //2个指针同时遍历
        if (list1 == null) {
            return list2;
        }
        if (list2 == null) {
            return list1;
        }
        //两个表合并后的表头head   当前合并的尾 tail
        ListNode head = new ListNode(), tail = head;
        while (list1 != null && list2 != null) {
            if (list1.val < list2.val) {
                //尾插法
                tail.next = list1;
                //断开原来指针
                list1 = list1.next;
            } else {
                tail.next = list2;
                list2 = list2.next;
            }
            tail = tail.next;
        }
        if (list1 != null) {
            tail.next = list1;
        } else if (list2 != null) {
            tail.next = list2;
        }
        return head.next;
    }

8)其他

###⑦复杂链表的复制
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
这里写图片描述

解题思路:用辅助空间O(n)来存储复制的链表。
1)根据原始链表,对每一个结点N创建它的复制结点N’,并插入到N之后。
这里写图片描述
2)复制random指针:对原始链表每一个random指针N->S,有N’->S’。
这里写图片描述
3)拆分为两个链表:奇数位为原始链表,偶数位为复制链表。
这里写图片描述
代码如下:

public class RandomListNode {
        int label;
        RandomListNode next = null;
        RandomListNode random = null;

        RandomListNode(int label) {
            this.label = label;
        }
    }

    public RandomListNode Clone(RandomListNode pHead) {
        if (pHead == null)
            return null;
        RandomListNode head = new RandomListNode(pHead.label);
        RandomListNode temp = head;
        while (pHead.next != null) {
            temp.next = new RandomListNode(pHead.next.label);
            if (pHead.random != null) {
                temp.random = new RandomListNode(pHead.random.label);
            }
            pHead = pHead.next;
            temp = temp.next;
        }
        return head;
    }

###⑧求两个单链表的第一个公共结点
输入两个链表,找出它们的第一个公共结点。

思路一:两个链表有公共结点,那么第一个公共结点之后的结点都是重合的。如下图:
这里写图片描述
对于两个链表,如果有公共结点,那么公共结点一定出现在尾部。
从尾部开始比较结点,最后一个相同的结点就是第一个公共结点。
为了实现这种后进先出,使用辅助栈来分别存储这两个单链表。
这种方法虽然可以实现,但是以空间换时间,空间复杂度大。
思路二:先求出链表长度,然后长的链表先走多出的几步,然后两个链表同时向下走去寻找相同的结点。
代码如下:需要遍历两遍,第一遍是为了同步指针。第二遍是保持两个指针同步遍历寻找相同的结点。

   public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
       ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        while (p1 != p2) {
            p1 = (p1 != null ? p1.next : pHead2);
            p2 = (p2 != null ? p2.next : pHead1);
        }
        return p1;
    }

###⑨
##3)循环链表
表中最后一个结点的指针指向头结点,只有头结点是固定的。
##4)双向链表
data *prior(直接前驱) *next(直接后继)
###①二叉搜索树和双向链表的转换
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

解题思路:
树的每个结点有2个指针指向它的左右子结点,调整这两个指针。
二叉搜索树的顺序:左子树总数小于父结点,右子树总是大于父结点。
所以调整方案为:原先指向左子树的指针为指向链表中前一个结点的指针,指向右子树的结点为指向链表中后一个结点的指针。这是一个递归的过程。
转换方案:定义一个链表的尾指针,递归处理左右子树,最后返回链表的头节点。

public class TreeNode {
        int val = 0;
        TreeNode left = null;
        TreeNode right = null;

        public TreeNode(int val) {
            this.val = val;

        }
    }

    public TreeNode Convert(TreeNode pRootOfTree) {
        TreeNode lastlist = covertNode(pRootOfTree, null);
        TreeNode pHead = lastlist;
        while (pHead != null && pHead.left != null) {
            pHead = pHead.left;
        }
        return pHead;
    }

    public TreeNode covertNode(TreeNode root, TreeNode lastlist) {
        if (root == null)
            return null;
        TreeNode cur = root;
        if (cur.left != null) {
            lastlist = covertNode(cur.left, lastlist);
        }
        cur.left = lastlist;
        if (lastlist != null) {
            lastlist.right = cur;
        }
        lastlist = cur;
        if (cur.right != null) {
            lastlist = covertNode(cur.right, lastlist);
        }
        return lastlist;
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值