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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SunshineTan/article/details/80977229

1,顺序表

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

2,单链表

1)概念

读取麻烦,增删简单。

头指针和尾指针无法决定链表长度。

2)定义

结点定义:

struct ListNode{
    int value;
    ListNode* next;
};

为链表末尾添加一个结点:

//C、C++代码
void addToTail(ListNode** pHead, int value){
    ListNode* pNew = new ListNode();
    pNew -> value = value;
    pNew -> next = NULL;

    if(*pHead == NULL){//空表
        *pHead = pNew;
    }else{
        ListNode* pNode = *pHead;
        while(pNode -> next != NULL)//遍历找到尾指针
            pNode = pNode->next;
        pNode -> next = pNew;
    }
}
//java代码
class ListNode{
        int val;
        ListNode next;
    }
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> list = new ArrayList<>();
        if (listNode == null)
            return list;
        Stack<ListNode> stack = new Stack<>();
        while (listNode != null) {
            stack.push(listNode);
            listNode = listNode.next;
        }
        while (!stack.isEmpty()) {
            list.add(stack.pop().val);
        }
        return list;
    }

    @Test
    public void test() {
        ListNode listNode0 = new ListNode();
        ListNode listNode1 = new ListNode();
        ListNode listNode2 = new ListNode();
        ListNode listNode3 = new ListNode();
        ListNode listNode4 = new ListNode();
        ListNode listNode5 = new ListNode();
        listNode0.val = 0;
        listNode0.next = listNode1;
        listNode1.val = 1;
        listNode1.next = listNode2;
        listNode2.val = 2;
        listNode2.next = listNode3;
        listNode3.val = 3;
        listNode3.next = listNode4;
        listNode4.val = 4;
        listNode4.next = listNode5;
        listNode5.val = 5;
        listNode5.next = null;
        System.out.print(printListFromTailToHead(listNode0));
    }

3)应用

①从尾到头打印链表

输入一个链表,从尾到头打印链表每个节点的值。
解题思路:由于打印是只读操作,不宜改变原数据格式。可以借助辅助栈,或者使用递归实现(注意链表太长会导致函数调用层级变深,溢出)。

②在O(1)时间删除链表结点

思路:要删除结点i,将i的下一个结点j的内容复制到i,然后把i的指针指向结点j的下一个结点。这样删除结点i,不需要知道i的前一个结点。(O(1))
若删除的结点位于链表的尾部,那么需要从头结点开始遍历,得到前序结点再删除。(O(n))
若链表只有一个结点且需删除,那么还需要在删除后把头结点置NULL。

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

public class ListNode{
        ListNode nextNode;
        int data;
    }
    public void deleteNode(ListNode head, ListNode deListNode) {
        if (deListNode == null || head == null)//链表为null
            return;
        if (head == deListNode) {//链表只有一个结点
            head = null;
        } else {

            if (deListNode.nextNode == null) {// 若删除节点是末尾节点,往后移一个
                ListNode pointListNode = head;
                while (pointListNode.nextNode.nextNode != null) {
                    pointListNode = pointListNode.nextNode;
                }
                pointListNode.nextNode = null;
            } else {//O(1)删除一个结点
                deListNode.data = deListNode.nextNode.data;
                deListNode.nextNode = deListNode.nextNode.nextNode;
            }
        }
    }

④链表中倒数第k个结点

输入一个链表,输出该链表中倒数第k个结点。
思路:定义一快一慢两个指针,快指针走K步,然后慢指针开始走,快指针到尾时,慢指针就找到了倒数第K个节点。
类似题目:
i>找中间节点:
使用两个指针,同时从头结点出发,一个走一步,一个走两步。
当走的快的结点到链表尾部时,走得慢的指针正好在链表中间。
如果链表中结点为奇数个,返回中间结点;如果是偶数,返回中间结点中的任意一个。
ii>判断一个单向链表是否 形成了环形结构:
使用两个指针,同时从头结点出发,一个走一步,一个走两步。
如果走的快的指针追上了走得慢的指针,那么就是环形链表。
如果走的快的指针走到链表的末尾都没有追上第一个指针,那么就不是环形。

 public class ListNode{
        ListNode next;
        int data;
    }
    public ListNode FindKthToTail(ListNode head,int k) {
        if (head == null || k <= 0) {
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;
        while(k-- > 1) {
            if (fast.next != null)
                fast = fast.next;
            else
                return null;
        }
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }

⑤反转链表

输入一个链表,反转链表后,输出链表的所有元素。
难点:可能出现断链。

class ListNode{
        ListNode next;
        int val;
    }
    public ListNode ReverseList(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode pPrev = null;
        while(head != null) {//head保存当前结点
            ListNode pNext = head.next;//保存当前结点的后一个结点
            head.next = pPrev;//翻转上一次的两个结点
            pPrev = head;//保存当前结点,是下一次循环的前一个结点
            head = pNext;//head指针后移,处理下一个结点
        }
        return pPrev;
    }

⑥链表合并

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

public class ListNode{
        ListNode next;
        int val;
    }
    public ListNode Merge(ListNode list1,ListNode list2) {
        if (list1 == null) {
            return list2;
        }
        if (list2 == null) {
            return list1;
        }
        ListNode newHead = null;
        if (list1.val <= list2.val) {
            newHead = list1;
            newHead.next = Merge(list1.next,list2);
        }else {
            newHead = list2;
            newHead.next = Merge(list1,list2.next);
        }
        return newHead;
    }

⑦复杂链表的复制

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的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;
    }

猜你喜欢

转载自blog.csdn.net/SunshineTan/article/details/80977229