数据结构(三)

上一篇 : 数据结构(二)
上节在数据结构(二)说了几个关于链表比较有思考性的算法,关于链表在原先周会学习的时候讲了链表的常见算法,所以接下继续把链表的几个常见的算法跟着写一下,因为我感觉除了数组外,就是链表了,所以讲几种链表常见的算法有利于自己写代码时思维发散,提高自己的编码能力,也可以提高代码的整体性能,减少时间损耗。
主要有两类:链表,和快速排序的思维方式

目录

  • 链表
    • 单链表反转(在前一节讲过)
    • 链表的中环检测
    • 两个有序链表的合并
    • 删除链表倒数第N个节点
    • 求链表的中间节点
  • 快速排序
    • 快排
    • 归并
链表
链表的中环检测

意思:给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

核心思路:

** 其实 这个意思就好比A和B两个人同时在操场跑圈,A的速度比B快那么几步,这样在绕着操场跑的时候,总有一个时间A会赶上B,然后A和B重合了,快慢指针就是这个意思.**

这个用java写有两个思路:一. 快慢指针法(这个可以是一个通用的思路),另外一个就是用map自定义一个下标,然后存储这个下标作为标记判断是否有死循环,
解决思路1:快慢指针法
这是最常见的方法。思路就是有两个指针P1和P2,同时从头结点开始往下遍历链表中的所有节点。

P1是慢指针,一次遍历一个节点。
P2是快指针,一次遍历两个节点。

如果链表中没有环,P2和P1会先后遍历完所有的节点。

如果链表中有环,P2和P1则会先后进入环中,一直循环,并一定会在在某一次遍历中相遇。

因此,只要发现P2和P1相遇了,就可以判定链表中存在环。

解决思路1:利用map自定义下标索引,然后遍历链表存储,这个就是给每个节点定义一个唯一的标识,就是利用顺序1,2,3,4,5这样区别的,把索引当做key,节点当做value,遍历时如果利用key取出值了,就证明已经死循环了.
上代码:

先声明一个实体类存储链表
class TreeNode{
    public int val;
    public TreeNode next;

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

    public TreeNode(int val, TreeNode next) {
        this.val = val;
        this.next = next;
    }

    @Override
    public String toString() {
        return "TreeNode{" +
                "val=" + val +
                ", next=" + next +
                '}';
    }
     public static void main(String[] args) {
        TreeNode node6 = new TreeNode(6);
        TreeNode node5 = new TreeNode(5,node6);
//        TreeNode node5 = new TreeNode(5);
        TreeNode node4 = new TreeNode(4,node5);
        TreeNode node3 = new TreeNode(3,node4);
        TreeNode node2 = new TreeNode(2,node3);
        TreeNode node1 = new TreeNode(1,node2);
     }
}
链表中环
 /**
     * 链表中环检测
     * 就是比如: 1->2->3->4->5->6->3
     * 这就死循环了
     * @param headNode
     * @return
     */
    public boolean LingkedZhongHuan(TreeNode headNode) {
        if (headNode == null) {
            return false;
        }

        TreeNode p = headNode;
        TreeNode q = headNode.next;

        // 快指针未能遍历完所有节点
        while (q != null && q.next != null) {
            p = p.next; // 遍历一个节点
            q = q.next.next; // 遍历两个个节点

            // 已到链表末尾
            if (q == null) {
                return false;
            } else if (p == q) {
                // 快慢指针相遇,存在环
                return true;
            }
        }
        return false;
    }

基于上面这个中环监测再补充一个
关于在学递归的时候,递归需要注意的几个条件, 首先递归本质的条件: 终止条件问题分解就不说了
需要注意的三点:

  • 警惕栈溢出
  • 防止重复条用
  • 中环监测, 防止数据库的脏数据从而引起了死循环
	//定义一个map用于存储查询结果
	private final static Map<Integer,Integer> hashKey = new HashMap();
	//定义一个深度值, 超过这个深度直接抛异常,避免引起栈溢出
    private static int depth = 0;
    public int f(int n){
        ++depth;
        if (depth > 1000){ throw new OutputLengthException("长度过长!!");}

        if (n == 1) {return 1;}
        if (n == 2) {return 2;}
		//每次递归前, 先判断一下map里有没有值,如果先前搜索过就直接取出来用
        if (hashKey.containsKey(n)){return hashKey.get(n);}
        int ret = f(n-1)+f(n-2);
        hashKey.put(n,ret);
        return ret;
    }

下面这个是中环监测 , 虽然上面有那个代码, 但是感觉不太严谨 , 随即又有自己的想法,当然可以结合自己要求参照用

//防止中环
public boolean zhonghuan(TreeNode headNode){
        if (headNode == null){
            return false;
        }
        TreeNode q = headNode.next;
        TreeNode s = headNode;
        while(q != null){
            if (q.next == null){
                return false;
            }

            if (s == q){
                return true;
            }
            q = q.next.next;
            s = s.next;
        }

        return false;
    }

两个有序链表的合并

其实就是把两个链表合并成一个链表,在合并的时候比较两个链表的对应值然后排序

1->5->3->7
2->6->4->8

1->2->5->6->3->4->7->8

可以反过来想 :假如是一个链表然后分而治之,然后排序,但这个分而治之只是分一次然后各个排序

/**
     * 两个链表有序合并
     * @param l1
     * @param l2
     * @return
     */
    public TreeNode mergeTwoLists(TreeNode l1, TreeNode l2) {
        if (l1 == null) return l2;
        if (l2 == null) return l1;

        TreeNode head = null;
        if (l1.val <= l2.val){
            head = l1;
            head.next = mergeTwoLists(l1.next, l2);
        } else {
            head = l2;
            head.next = mergeTwoLists(l1, l2.next);
        }
        return head;
    }
删除链表倒数第N个节点

就是一个链表删除倒数第n个节点并返回这个链表
例如: 1->2->3->4->5->6 n=2
删除 5 ,结果:1->2->3->4->6

核心思路:

其实也是用的快慢指针的思想: 一个链表节点分别赋值个两个暂存的对象–就是A和B
然后要删除倒数n个节点, 先让A走n步,然后在遍历A的同时也让B遍历,这样当A遍历完的时候,B恰好遍历到那个倒数第n个节点,然后直接返回.

当然中间也是要有许多判断的,避免链表是空的或者遍历后是空的

上代码

/**
     *链表删除倒数第n个节点并返回这个链表
     * 例如: 1->2->3->4->5->6  n=2
     * 删除 5 ,结果:1->2->3->4->6
     * @param head
     * @return
     */
    public TreeNode middleRemoveNodeWithN(TreeNode head ,int n){
        TreeNode p=head;
        TreeNode q=head;
        for(int i=0;i<n;i++){
            p=p.next;
        }
        if(p==null){
            head=head.next;
            return head;
        }
        while(p.next!=null){
            p=p.next;
            q=q.next;
        }
        q.next=q.next.next;
        return head;
    }
求链表的中间节点

可以这样理解,就是求链表删除中间节点并返回这个节点
例如:
奇数: 1->2->3->4->5 删除3 并返回3
偶数: 1->2->3->4->5->6 删除4 并返回4

核心::
其实也是用的快慢指针的思想,A和B,遍历A的同时也遍历B,但是让A比B快一步,就是让A每次遍历的步数都是B的两倍

这里用两种方式实现: 1.普通的遍历循环 ,2递归 但是思路是一样的

上代码:

 /**
     *链表删除中间节点并返回这个节点
     * 1->2->3->4->5   删除3 并返回3
     * 1->2->3->4->5->6   删除4 并返回4
     * @param head
     * @return
     */
    public TreeNode middleRemoveNode(TreeNode head){
        if (head == null || head.next == null) return head;
        TreeNode slow = head;
        TreeNode fast = head;

        while (fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }

        return fast == null ? slow:slow.next;
    }
    /**
     *链表删除中间节点并返回这个节点- 递归
     * @param head
     * @return
     */
    public TreeNode middleRemoveNodeByRecuser(TreeNode head){
        if (head == null || head.next == null || head.next.next == null) return head;

        TreeNode fast = head.next.next;
        if (fast.next == null || fast.next.next == null) return head.next;
        TreeNode middleNode = middleRemoveNodeByRecuser(head.next);

        return middleNode;
    }
快速排序
归并排序

归并排序算法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

代码:

package com.fufu.algorithm.sort;

import java.util.Arrays;
public class MergeSort {

    public static int[] sort(int [] a) {
        if (a.length <= 1) {
            return a;
        }
        int num = a.length >> 1;
        int[] left = Arrays.copyOfRange(a, 0, num);
        int[] right = Arrays.copyOfRange(a, num, a.length);
        return mergeTwoArray(sort(left), sort(right));
    }

    public static int[] mergeTwoArray(int[] a, int[] b) {
        int i = 0, j = 0, k = 0;
        int[] result = new int[a.length + b.length]; // 申请额外空间保存归并之后数据

        while (i < a.length && j < b.length) { //选取两个序列中的较小值放入新数组
            if (a[i] <= b[j]) {
                result[k++] = a[i++];
            } else {
                result[k++] = b[j++];
            }
        }

        while (i < a.length) { //序列a中多余的元素移入新数组
            result[k++] = a[i++];
        }
        while (j < b.length) {//序列b中多余的元素移入新数组
            result[k++] = b[j++];
        }
        return result;
    }

    public static void main(String[] args) {
        int[] b = {3, 1, 5, 4};
        System.out.println(Arrays.toString(sort(b)));
    }
}
快速排序

俗称挖坑法, 就是先拿一个基数,然后两边开始遍历,左边比右边大了填坑填到右边去,反之填到左边. 但是要注意必须是先遍历左边,如果先遍历右边,在填坑的时候,到最后就会发现坑填不了,因为排序大小不对了.
上代码:

 /**
     *  快速排序(挖坑法递归)
     * @param arr   待排序数组
     * @param low   左边界
     * @param high  右边界
     */
    public static void sort(int arr[], int low, int high) {
        if (arr == null || arr.length <= 0) {
            return;
        }
        if (low >= high) {
            return;
        }

        int left = low;
        int right = high;
        int temp = arr[left]; //挖坑1:保存基准的值

        while (left < right) {
            while (left < right && arr[right] >= temp) {
                right--;
            }
            arr[left] = arr[right]; //坑2:从后向前找到比基准小的元素,插入到基准位置坑1中
            while (left < right && arr[left] <= temp) {
                left ++;
            }
            arr[right] = arr[left]; //坑3:从前往后找到比基准大的元素,放到刚才挖的坑2中
        }
        arr[left] = temp; //基准值填补到坑3中,准备分治递归快排
        System.out.println("Sorting: " + Arrays.toString(arr));
        sort(arr, low, left-1);
        sort(arr, left + 1, high);
    }

总结一下上述的算法其实核心都是有一个公式,就是每个代码它的实现的核心就是那一行或者两行,其他都是辅助的代码,而那个核心的代码其实就是一个数学公式的表达式.
所以说明, 数学思维方式很重要,这个有时间要好好巩固一下.

下一篇 :数据结构(四)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值