【Leetcode刷题篇/面试篇】-排序算法篇

本文深入讲解了各种排序算法,包括冒泡排序、选择排序、插入排序、归并排序、快速排序、希尔排序、堆排序等基于比较的排序算法,以及计数排序、桶排序、基数排序等非基于比较的排序算法。每种算法都附有详细的执行流程和代码实现。

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

数据结构-排序算法篇

开篇:学好数据结构与算法,面到哪里都不怕。

在这里插入图片描述

一、初识排序算法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kdiNLyQd-1605958143761)(D:\software\typora\workplace\imgs_sort\1.png)]

二、冒泡排序(Bubble Sort)

流程:

  • 1。从头开始比较每一对相邻元素,如果第一个比第二个大,就交换它们的位置。执行完一遍,最末尾的那个元素就是最大元素。
  • 2.忽略1中曾经找到的元素,重复执行1,直到全部元素都有序。
public class BubbleSort {
	public static void  bubbleSort(int[]  arr) {
		if(arr.length<2||arr==null) {
			return;
		}
		
		//冒泡排序
		for(int end=arr.length-1;end>0;end--) {
			for(int i=0;i<end;i++) {
				if(arr[i]>arr[i+1]) {
					swap(arr,i,i+1);
				}
			}
		}
	}
	public static void swap(int[] arr,int i,int j) {
		int temp = arr[i];
		arr[i]   = arr[j];
		arr[j]   = temp;
	}
	
	public static void main(String[] args) {
		int[] arr = {56,9,10,19,28,37,34};
		bubbleSort(arr);
		for(int number:arr) {
			System.out.print(number + " ");
		}
	}
}

稳定性:如果相等的2个元素,在排序前后的相对位置保持不变,那么这是稳定的排序算法

其中冒泡排序是稳定的算法。

冒泡排序的平均复杂度是O(n^2),空间复杂度为O(1).

二、选择排序(Selection Sort)

执行流程:

  • 1.从序列中找出最小的那个元素,然后与最首的元素交换位置 ✓ 执行完一轮后,最首尾的那个元素就是最小的元素
  • 2.忽略 1中曾经找到的最小元素,重复执行步骤 ①
public class SelectionSort {
	public static void selectionSort(int[] arr) {
		if(arr==null||arr.length<2) {
			return;
		}
		
		for(int i=0;i<arr.length-1;i++) {
			int minIndex = i;
			for(int j=i+1;j<arr.length;j++) {
				minIndex = arr[j]<arr[minIndex]?j:minIndex;
			}
			swap(arr,i,minIndex);
		}
	}
	
	public static void swap(int[] arr,int i,int j) {
		int temp = arr[i];
		arr[i]   = arr[j];
		arr[j]   = temp;
	}
	
	public static void main(String[] args) {
		int[] arr = {56,9,10,19,28,37,34};
		selectionSort(arr);
		for(int number:arr) {
			System.out.print(number + " ");
		}
	}
}

选择排序是不稳定算法

平均时间复杂度为O(n^2) 空间复杂度为O(1)

三、堆排序

可认为堆排序是对选择排序的改进。

执行流程:

  • 1.对数组原地建立一个大顶堆
  • 2.重复执行以下操作,直到堆元素为1
    • 交换堆顶元素与尾元素
    • 然后下沉
// 下沉方法
	public static void siftDown(int[] arr,int parentIndex,int length) {
		int temp = arr[parentIndex];
		int childIndex = 2*parentIndex+1;
		while(childIndex<length) {
			//查看右孩子是否存在
			childIndex = childIndex+1<length&&arr[childIndex+1]>arr[childIndex]?childIndex+1:childIndex;
			if(arr[childIndex]<=temp) {
				break;
			}
			arr[parentIndex] = arr[childIndex];
			parentIndex = childIndex;
			childIndex = 2 * parentIndex + 1;
		}
		arr[parentIndex] = temp;
	}
	
	public static void heapSort(int[] arr) {
		// 1.根据数组建立一个大顶堆
		for(int i=(arr.length-1)/2;i>=0;i--) {
			// 下沉
			siftDown(arr,i,arr.length);
		}
		
		// 2.大顶堆置换到最后的索引
		for(int j=arr.length-1;j>0;j--) {
			swap(arr,0,j);
			siftDown(arr, 0, j);
		}
	}
	
	// 交换
	public static void swap(int[] arr,int i,int j) {
		int temp = arr[i];
		arr[i]   = arr[j];
		arr[j]   = temp;
	}
	
	// 主函数
	public static void main(String[] args) {
		int[] arr = {56,9,10,19,28,37,34};
		heapSort(arr);
		for(int number:arr) {
			System.out.print(number + " ");
		}
	}

堆排序是不稳定算法

平均时间复杂度为O(nlogn) 空间复杂度为O(1)

四、插入排序

执行流程

  • ·1.在执行过程中,插入排序会将序列分为2部分 ✓ 头部是已经排好序的,尾部是待排序的
    1. 从头开始扫描每一个元素 ✓ 每当扫描到一个元素,就将它插入到头部合适的位置,使得头部数据依然保持有序
public class InsertSort {
	public static void insertSort(int[] arr) {
		if(arr==null || arr.length<2) {
			return;
		}
		for(int i=1;i<arr.length;i++) {
			for(int j=i-1;j>=0&&arr[j]>arr[j+1];j--) {
				swap(arr,j,j+1);
			}
		}
	}
	
	public static void swap(int[] arr,int i,int j) {
		int temp = arr[i];
		arr[i]   = arr[j];
		arr[j]   = temp;
	}
	// 主函数

	public static void main(String[] args) {
		int[] arr = {56,9,10,19,28,37,34};
		insertSort(arr);
		for(int number:arr) {
			System.out.print(number + " ");
		}
	}
}

优化思路:往已排序好的中插入未排序的, 那么是否可用二分搜索来找寻位置

插入排序是不稳定算法

平均时间复杂度为O(n^2) 空间复杂度为O(1)

补充知识点:链表的插入排序

public ListNode insertionSortList(ListNode head) {
        // 链表
        if(head==null||head.next==null){
            return head;
        }
        ListNode dummy = new ListNode(-1);
        dummy.next = head;

        while(head!=null&&head.next!=null){
            // 先找到没排好序的
            if(head.val<=head.next.val){
                head = head.next;
                continue;
            }
            // 然后对其找前后
            ListNode pre = dummy;
            while(pre.next.val<head.next.val)pre=pre.next;
            ListNode cur = head.next;
            //换指针
            head.next = cur.next;
            cur.next  = pre.next;
            pre.next = cur;
        }
        return dummy.next;
    }

补充知识点:二分搜索查找

1.寻找一个数(简单的二叉搜索)

搜索一个数,如果存在,返回其索引,否则返回 -1。

public static int  binarySearch(int[] arr,int target) {
    int left = 0;
    int right = arr.length-1;
    while(left<=right) {
        int mid = left + ((right-left)>>1);
        if(arr[mid]==target) {
            return mid;
        }else if(arr[mid]<target) {
            left = mid + 1;
        }else if(arr[mid]>target) {
            right = mid-1;
        }
    }
    return -1;
}

但存在一个缺点,比如说给你有序数组 nums = [1,2,2,2,3]target 为 2,此算法返回的索引是 2,没错。但是如果我想得到 target 的左侧边界,即索引 1,或者我想得到 target 的右侧边界,即索引 3,这样的话此算法是无法处理的。

2.查找一个数的左边界

	public static int left_Bound(int[] arr,int target) {
		int left = 0;
		int right = arr.length-1;
		while(left<=right) {
			// 查找中间值
			int mid = left + ((right-left)>>1);
			if(arr[mid]>target) {
				right = mid -1;
			}else if(arr[mid]<target) {
				left = mid + 1;
			}else if(arr[mid]==target) {
				// 控制边界
				right = mid-1;
			}
		}
		// 判断
		if(left>=arr.length||arr[left]!=target) {
			return -1;
		}
		return left;
	}

3.查找一个数的右边界

	public static int left_Bound(int[] arr,int target) {
		int left = 0;
		int right = arr.length-1;
		while(left<=right) {
			// 查找中间值
			int mid = left + ((right-left)>>1);
			if(arr[mid]>target) {
				right = mid -1;
			}else if(arr[mid]<target) {
				left = mid + 1;
			}else if(arr[mid]==target) {
				// 控制边界
				left = mid+1;
			}
		}
		// 判断
		if(right<0 ||arr[right]!=target) {
			return -1;
		}
		return right;
	}

五、归并排序

执行流程:

  • 1.不断地将当前序列平均分割成2个子序列,直到不能在分割
  • 2.不断将2个子序列合并成一个有序序列
public class MergeSort {
	public static void mergeSort(int[] arr) {
		if(arr==null||arr.length<2) {
			return;
		}
		sortProcess(arr,0,arr.length-1);
	}
	
	public static void sortProcess(int[] arr,int L,int R) {
		if(L==R){
			return;
		}
		int mid = L + ((R-L)>>1);
		sortProcess(arr, L, mid);
		sortProcess(arr, mid+1, R);
		merge(arr,L,mid,R);
	}
	
	public static void merge(int[] arr,int L,int mid,int R) {
		int p1 = L;
		int p2 = mid+1;
		int[] temp = new int[R-L+1];
		int i=0;
		while(p1<=mid&&p2<=R) {
			temp[i++] = arr[p1]<arr[p2]?arr[p1++]:arr[p2++];
		}
		while(p1<=mid) {
			temp[i++] = arr[p1++];
		}
		while(p2<=R) {
			temp[i++] = arr[p2++];
		}
		for(int j=0;j<temp.length;j++) {
			arr[L+j] = temp[j];
		}
	}
	
	// 主函数
	public static void main(String[] args) {
		int[] arr = {56,9,10,19,28,37,34};
		mergeSort(arr);
		for(int number:arr) {
			System.out.print(number + " ");
		}
	}
}

归并排序是稳定算法

平均时间复杂度为O(nlogn) 空间复杂度为O(n)

6.1 小和问题

在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组 的小和。

例子:

[1,3,4,2,5]

1左边比1小的数,没有;

3左边比3小的数,1;

4左边比4小的数,1、3;

2左边比2小的数,1;

5左边比5小的数,1、3、4、2;

所以小和为1+1+3+1+1+3+4+2=16

	// 归并排序的步骤
	public static int mergeSort(int[] arr) {
		if(arr==null||arr.length<2) {
			return 0;
		}
		// 开始
		int res = sortProcess(arr,0,arr.length-1);
		return res;
	}
	
	public static int sortProcess(int[] arr,int L,int R) {
		// 递归截止条件
		if(L==R) {
			return 0;
		}
		int mid = L + ((R-L)>>1);
		
		
		// 合并
		return sortProcess(arr, L, mid) + sortProcess(arr, mid+1, R) + merge(arr,L,mid,R); 
	}
	
	// 合并 
	public static int merge(int[] arr,int L,int mid,int R) {
		int p1 = L;
		int p2 = mid+1;
		
		// 结果
		int res = 0;
		// 新建
		int[] temp = new int[R-L+1];
		int index = 0;
		while(p1<=mid&&p2<=R) {
			res			 += arr[p1]<arr[p2]?(R-p2+1)*(arr[p1]):0;
			temp[index++] = arr[p1]<arr[p2]?arr[p1++]:arr[p2++];
		}
		while(p1<=mid) {
			temp[index++] = arr[p1++];
		}
		while(p2<=R) {
			temp[index++] = arr[p2++];
		}
		// 回归
		for(int i=0;i<temp.length;i++) {
			arr[L+i] = temp[i];
		}
		
		return res;
	}
	

6.2 逆序对问题

	public static int mergeSort(int[] arr) {
		if(arr==null||arr.length<2) {
			return 0;
		}
		return sortProcess(arr,0,arr.length-1);
	}
	
	public static int sortProcess(int[] arr,int L,int R) {
		if(L==R) {
			return 0;
		}
		int mid = L + ((R-L)>>1);
		int leftRes  = sortProcess(arr, L, mid);
		int rightRes = sortProcess(arr, mid+1, R);
		return leftRes + rightRes + merge(arr,L,mid,R);
	}
	
	// 合并
	public static int merge(int[] arr,int L,int mid,int R) {
		int p1 = L;
		int p2 = mid+1;
		int i = 0;
		int[] temp = new int[R-L+1];
		// 结果
		int res = 0;
		
		while(p1<=mid&&p2<=R) {
			res       += arr[p1]>arr[p2]?(mid-p1+1):0;
			temp[i++] = arr[p1]<arr[p2]?arr[p1++]:arr[p2++];
		}
		while(p1<=mid) {
			temp[i++] = arr[p1++];
		}
		while(p2<=R) {
			temp[i++] = arr[p2++];
		}
		
		for(i=0;i<temp.length;i++) {
			arr[L+i] = temp[i];
		}
		
		return res;
	}

6.3 合并两个有序数组

public void merge(int[] nums1, int m, int[] nums2, int n) {
        int p1 = 0;
        int p2 = 0;
        // 开辟一个新的数组
        int[] temp = new int[m+n];
        int i  = 0;
        // 开始
        while(p1<=m-1&&p2<=n-1){
            temp[i++] = nums1[p1]<nums2[p2]?nums1[p1++]:nums2[p2++];
        }
        while(p1<=m-1){
            temp[i++] = nums1[p1++];
        }
        while(p2<=n-1){
            temp[i++] = nums2[p2++];
        }
        // 最终
        for(i=0;i<temp.length;i++){
            nums1[i] = temp[i];
        }
    }

6.4 合并两个有序链表

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(-1);
        ListNode pre = dummy;
        // 对其遍历
        while(l1!=null&&l2!=null){
            if(l1.val<l2.val){
                pre.next = l1;
                l1 = l1.next;
                pre = pre.next;
            }else{
                pre.next = l2;
                l2 = l2.next;
                pre = pre.next;
            }
        }

        pre.next = l1!=null?l1:l2;
        return dummy.next;
    }

6.5 合并K个有序链表

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

// 新建封装类
    class Status implements Comparable<Status>{
        int val;
        ListNode node;

        Status(int val,ListNode node){
            this.val = val;
            this.node = node;
        }

        public int compareTo(Status status2){
            return this.val - status2.val;
        }
    }
    public ListNode mergeKLists(ListNode[] lists) {
        if(lists==null){
            return null;
        }
        if(lists.length==1){
            return lists[0];
        }
        // 优先级队列  小顶堆
        PriorityQueue<Status> queue = new PriorityQueue<>();
        // 入队列
        for(int i=0;i<lists.length;i++){
            if(lists[i]!=null){
                queue.offer(new Status(lists[i].val,lists[i]));
            }
        }

        // 合并K个链表
        ListNode dummy = new ListNode(-1);
        ListNode pre   = dummy;
        // 对其遍历
        while(!queue.isEmpty()){
            Status status = queue.poll();
            pre.next = status.node;
            pre = pre.next;
            // 判断是否还有
            if(status.node.next!=null){
                queue.offer(new Status(status.node.next.val,status.node.next));
            }
        }

        return dummy.next;
    }

6.6 链表归并排序

public ListNode sortList(ListNode head) {
        return mergeSort(head,null);
    }
    public ListNode mergeSort(ListNode head,ListNode tail){
        // 截止条件
        if(head==null){
            return null;
        }
        if(head.next==tail){
            head.next = null;
            return head;
        }

        // 找mid
        // 快慢指针
        ListNode slow = head,fast = head;
        while(fast!=tail&&fast.next!=tail){
            slow = slow.next;
            fast = fast.next.next;
        }
        ListNode mid = slow;
        ListNode left = mergeSort(head,mid);
        ListNode right = mergeSort(mid,tail);
        ListNode sorted =  merge(left,right);
        return sorted;
    }

    // 合并两个有序链表
    public ListNode merge(ListNode head1,ListNode head2){
        ListNode dummy = new ListNode(-1);
        ListNode pre = dummy;

        while(head1!=null&&head2!=null){
            if(head1.val<head2.val){
                pre.next = head1;
                head1 = head1.next;
                pre = pre.next;
            }else{
                pre.next = head2;
                head2 = head2.next;
                pre = pre.next;
            }
        }

        pre.next = head1!=null?head1:head2;
        return dummy.next;
    }

六、快速排序

6.1 二分问题 移动0

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:

必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。

// 移动0的二分问题
	public static void moveZero(int[] arr) {
		if(arr==null||arr.length<2) {
			return;
		}
		
		int less = -1;
		int cur  =0;
		while(cur<arr.length) {
			if(arr[cur]==0) {
				cur++;
			}else {
				swap(arr,++less,cur);
			}
		}
	}
	
	// 交换
	public static void  swap(int[] arr,int i,int j) {
		arr[i] = arr[i]^arr[j];
		arr[j] = arr[j]^arr[i];
		arr[i] = arr[i]^arr[j];
	}

6.2 荷兰国旗问题

给定一个数组arr,和一个数num,请把小于num的数放在数组的左边, 等于num的数放在数组的中间,大于num的数放在数组的右边。要求时间复杂度为O(N)

// 三分问题
	public static void three_process(int[] arr,int number) {
		if(arr==null||arr.length<2) {
			return;
		}
		process(arr,0,arr.length-1,number);
	}
	public static void process(int[] arr,int L,int R,int number) {
		
		int less = L-1;
		int more = R+1;
		while(L<more) {
			if(arr[L]<number) {
				swap(arr,++less,L++);
			}else if(arr[L]>number) {
				swap(arr,--more,L);
			}else if(arr[L]==number) {
				L++;
			}
		}
	}

6.3 快速排序问题

public static void quickSort(int[] arr) {
		if(arr==null||arr.length<2) {
			return;
		}
		quickSort(arr,0,arr.length-1);
	}
	
	public static void quickSort(int[] arr,int L,int R) {
		if(L<R) {
            // 随机排序
            swap(arr,L+(int)(Math.random()*(R-L+1)),R);
			int[] p = partition(arr,L,R);
			quickSort(arr,L,p[0]-1);
			quickSort(arr,p[1]+1,R);
		}
	}
	
	public static int[] partition(int[] arr,int L,int R) {
		int less = L-1;
		int more = R;
		while(L<more) {
			if(arr[L]<arr[R]) {
				swap(arr,++less,L++);
			}else if(arr[L]>arr[R]) {
				swap(arr,--more,L);
			}else{
				L++;
			}
		}
		swap(arr,more,R);
		return new int[] {less+1,more};
	}
	

快速排序不稳定排序

O(nlogn) O(logn)

七、希尔排序(了解)

流程

希尔排序把序列看作是一个矩阵,分成 𝑚 列,逐列进行排序 𝑚 从某个整数逐渐减为1 当 𝑚 为1时,整个序列将完全有序

实例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Egr7A7EA-1605958143771)(D:\software\typora\workplace\imgs_sort\2.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tnlwaTTQ-1605958143773)(D:\software\typora\workplace\imgs_sort\3.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-abWIUhZm-1605958143777)(D:\software\typora\workplace\imgs_sort\4.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e8NwqlbK-1605958143786)(D:\software\typora\workplace\imgs_sort\5.png)]


public class ShellSort<T extends Comparable<T>> extends Sort<T> {

	@Override
	protected void sort() {
		List<Integer> stepSequence = sedgewickStepSequence();
		for (Integer step : stepSequence) {
			sort(step);
		}
	}
	
	/**
	 * 分成step列进行排序
	 */
	private void sort(int step) {
		// col : 第几列,column的简称
		for (int col = 0; col < step; col++) { // 对第col列进行排序
			// col、col+step、col+2*step、col+3*step
			for (int begin = col + step; begin < array.length; begin += step) {
				int cur = begin;
				while (cur > col && cmp(cur, cur - step) < 0) {
					swap(cur, cur - step);
					cur -= step;
				}
			}
		}
	}
	
	private List<Integer> shellStepSequence() {
		List<Integer> stepSequence = new ArrayList<>();
		int step = array.length;
		while ((step >>= 1) > 0) {
			stepSequence.add(step);
		}
		return stepSequence;
	}
	
	private List<Integer> sedgewickStepSequence() {
		List<Integer> stepSequence = new LinkedList<>();
		int k = 0, step = 0;
		while (true) {
			if (k % 2 == 0) {
				int pow = (int) Math.pow(2, k >> 1);
				step = 1 + 9 * (pow * pow - pow);
			} else {
				int pow1 = (int) Math.pow(2, (k - 1) >> 1);
				int pow2 = (int) Math.pow(2, (k + 1) >> 1);
				step = 1 + 8 * pow1 * pow2 - 6 * pow2;
			}
			if (step >= array.length) break;
			stepSequence.add(0, step);
			k++;
		}
		return stepSequence;
	}
}

冒泡、选择、插入、归并、快速、希尔、堆排序,都是基于比较的排序

计数排序、桶排序、基数排序,都不是基于比较的排序

它们是典型的用空间换时间,在某些时候,平均时间复杂度可以比 O nlogn 更低

八、计数排序(了解)

统计每个整数在序列中出现的次数,进而推导出每个整数在有序序列中的索引。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iofF0UJr-1605958143788)(D:\software\typora\workplace\imgs_sort\6.png)]

protected void sort0() {
		// 找出最大值
		int max = array[0];
		for (int i = 1; i < array.length; i++) {
			if (array[i] > max) {
				max = array[i];
			}
		} // O(n)
		
		// 开辟内存空间,存储每个整数出现的次数
		int[] counts = new int[1 + max];
		// 统计每个整数出现的次数
		for (int i = 0; i < array.length; i++) {
			counts[array[i]]++;
		} // O(n)
		
		// 根据整数的出现次数,对整数进行排序
		int index = 0;
		for (int i = 0; i < counts.length; i++) {
			while (counts[i]-- > 0) {
				array[index++] = i;
			}
		} // O(n)

九、基数排序(了解)

  • 基数排序非常适合用于整数排序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HDuHURZc-1605958143790)(D:\software\typora\workplace\imgs_sort\7.png)]

rotected void sort() {
		// 找出最大值
		int max = array[0];
		for (int i = 1; i < array.length; i++) {
			if (array[i] > max) {
				max = array[i];
			}
		}
		
		// 个位数: array[i] / 1 % 10 = 3
		// 十位数:array[i] / 10 % 10 = 9
		// 百位数:array[i] / 100 % 10 = 5
		// 千位数:array[i] / 1000 % 10 = ...

		for (int divider = 1; divider <= max; divider *= 10) {
			countingSort(divider);
		}
	}
	
	protected void countingSort(int divider) {
		// 开辟内存空间,存储次数
		int[] counts = new int[10];
		// 统计每个整数出现的次数
		for (int i = 0; i < array.length; i++) {
			counts[array[i] / divider % 10]++;
		}
		// 累加次数
		for (int i = 1; i < counts.length; i++) {
			counts[i] += counts[i - 1];
		}
		
		// 从后往前遍历元素,将它放到有序数组中的合适位置
		int[] newArray = new int[array.length];
		for (int i = array.length - 1; i >= 0; i--) {
			newArray[--counts[array[i] / divider % 10]] = array[i];
		}
		
		// 将有序数组赋值到array
		for (int i = 0; i < newArray.length; i++) {
			array[i] = newArray[i];
		}
	}

十、桶排序

执行流程

① 创建一定数量的桶(比如用数组、链表作为桶)

② 按照一定的规则(不同类型的数据,规则不同),将序列中的元素均匀分配到对应的桶

③ 分别对每个桶进行单独排序

④ 将所有非空桶的元素合并成有序序列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pW9WrgmQ-1605958143792)(D:\software\typora\workplace\imgs_sort\8.png)]

10.1 利用桶排序排序后最大差值问题

题目:给定个数组,求如果排序之后,相邻两数的最大差值,要求时间复杂度O(N),且要求不能用非基于比较的排序。

给出三个桶,放最大值 最小值 是否有值 平均分配桶

package com.lcz.leetcode;
import java.util.*;

public class MaxGap {
	// 求最大差值 用桶排序
	public static int maxGap(int[] nums) {
		if(nums==null || nums.length<2) {
			return 0;
		}
		int len = nums.length;
		int min = Integer.MAX_VALUE;
		int max = Integer.MIN_VALUE;
		for(int i=0;i<len;i++) {
			min = Math.min(min, nums[i]);
			max = Math.max(max, nums[i]);
		}
		if(min==max) {
			return 0;
		}
		// 放入桶中
		boolean[] hasNum = new boolean[len+1];
		int[] maxs = new int[len+1];
		int[] mins = new int[len+1];
		int bid = 0;
		for(int i=0;i<len;i++) {
			bid = bucket(nums[i], len, min, max);
			mins[bid] = hasNum[bid]?Math.min(mins[bid], nums[i]):nums[i];
			maxs[bid] = hasNum[bid]?Math.max(maxs[bid], nums[i]):nums[i];
			hasNum[bid] =  true;
		}
		// 得到结果
		int res = 0;
		int lastMax = maxs[0];
		for(int i=1;i<=len;i++) {
			if(hasNum[i]) {
				res = Math.max(res, mins[i]-lastMax);
				lastMax = maxs[i];
			}
		}
		return res;
	}
	
	// 确定哪个桶
	public static int bucket(long num,long len,long min,long max) {
		return (int)((num-min)*len / (max-min));
	}
	
	
	// 标准方法
	public static int comparator(int[] nums) {
		if(nums==null||nums.length<2) {
			return 0;
		}
		// 对其排序
		Arrays.sort(nums);
		// 求最大差值
		int gap = Integer.MIN_VALUE;
		for(int i=1;i<nums.length;i++) {
			gap = Math.max(nums[i]-nums[i-1], gap);
		}
		return gap;
	}
	// 主函数
	public static void main(String[] args) {
		int[] arr = new int[] {1,3,2,6,5,7,8,9,0};
//		System.out.println(comparator(arr));
		System.out.println(maxGap(arr));
	}
}

总结、工程中的综合排序算法

  • 基础类型,对于稳定性要求不高
    • 对于少于60个的,直接用插入排序。虽然插入排序是O(n^2),但是这是在忽略常数项得到的。在60以内,插入排序的劣势体现不出来,其常数项很低。如果一开始数组容量很大,可分治处理,分治后小于60个采用这个。
    • 对于大于60的,用快速排序
  • 自定义类型对于稳定性有要求,因此用归并排序,因为需要保持原始顺序。

轶事-休眠排序

	// 休眠排序
	private static class SortThread extends Thread{
		private int value;
		public SortThread(int value) {
			this.value = value;
		}
		public void run() {
			try {
				Thread.sleep(value);
				System.out.println(value);
			}catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	// 主函数
	public static void main(String[] args) {
		int[] arr = {10,100,50,30,60};
		for(int i=0;i<arr.length;i++) {
			new SortThread(arr[i]).start();
		}
	}
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mind_programmonkey

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值