排序算法
1.冒泡排序
# 冒泡排序;对相邻的进行比较,把大的往后放,第一轮循环下来可以找到最大的,时间复杂度为O(n^2) def bubble_sort(arr): for i in range(len(arr)): for j in range(len(arr)-1-i): if arr[j]>arr[j+1]: temp = arr[j+1] arr[j+1] = arr[j] arr[j] = temp return arr ret = bubble_sort([1,5,3,2,4,6,2,1,5])
2.插入排序
# 从下标1开始,依次取出后面的元素和前面的元素相比较,直到元素比左边的大,比右边的小时,则插入;时间复杂度为O(n^2) def insertion_sort(arr): for i in range(1,len(arr)): # 从下标1开始向前比较 loop = i # 前面有几个数最大循环几轮 while(loop): if arr[i]<arr[i-1]: arr[i-1], arr[i] = arr[i], arr[i-1] # 从后往前交换元素 i -= 1 else: break loop -= 1 return arr
二分插入排序(查找的时候使用二分查找,而不是逐位向前查找)
希尔排序
希尔排序克服了插入排序中当最小的数在最右边的时候,需要把这个数逐位移到第一位的弊端;
希尔排序引入了增量序列,对每个增量序列下的子数组进行大间隔插入排序,随着后面的间隔越来越小,数组逐渐趋于部分有序
import java.util.Arrays; import java.util.Random; public class ShellSorted { public static void main(String[] args){ int[] array = new int[15]; for(int i=0; i<array.length; i++) array[i] = new Random().nextInt(20); System.out.println("排序前: " + Arrays.toString(array)); shell(array); System.out.println("排序后: " + Arrays.toString(array)); } public static void shell(int[] arr){ int len = arr.length; int h = 1; // 构造一个尾数为1的固定递增序列,递增序列有几个数表示要循环几轮 // 分别对每一轮使用跳跃间隔为h的插入排序, 最后一轮的h=1表示进行一次完整的插入排序 while(h<len/3) h = 3*h + 1; // 1, 4, 13... while(h>=1){ for(int i=h; i<len; i++) for(int j=i; j-h>=0 && arr[j]<arr[j-h]; j-=h){ int temp = arr[j]; arr[j] = arr[j-h]; arr[j-h] = temp; } h = h/3; } /* // 使用不同的递增序列 for(int k=len/2; k>=1; k=k/2) for(int i=k; i<len; i++) for(int j=i; j-k>=0 && arr[j]<arr[j-k]; j-=k){ int temp = arr[j]; arr[j] = arr[j-k]; arr[j-k] = temp; } */ } }
3.选择排序
# 选择排序,每次找出一个最小值,再把他放到最开始的位置(或加入一个新的数组),时间复杂度 O(n^2) def find_min(arr): min_num = arr[0] for i in arr: if i<min_num: min_num = i return min_num def selection_sort(arr): newarr = [] count = len(arr) while count: # 这里虽然改变了列表的长度,但是没影响 ret = find_min(arr) newarr.append(ret) arr.remove(ret) count -= 1 return newarr # 写法二(不需要额外的空间) def selectSort(arr): for i in range(len(arr)): min = i for j in range(i+1, len(arr)): if(arr[j]<arr[min]): min = j # arr[i] = arr[i] + arr[min] # arr[min] = arr[i] - arr[min] # 如果min没有变的话,此时的 min==i;不要乱用 # arr[i] = arr[i] - arr[min] temp = arr[i] arr[i] = arr[min] arr[min] = temp return arr # 写法三 def selection_sort_2(arr): for i in range(len(arr)): for j in range(i+1, len(arr)): if arr[j] < arr[i]: # 这个是多次交换,上面的是一次交换; temp = arr[j] arr[j] = arr[i] arr[i] = temp return arr
4.快速排序
# 随机选择一个值,把数组分为小于这个值的部分和大于的部分,再对这两部分递归 # 快速排序算法的平均复杂度为O(nlog(n))(递归log(n)层,每层为O(n)),最糟复杂度为O(n^2) def quick_sort(arr): if len(arr) < 2: return arr else: flag = arr[0] bigger = [i for i in arr[1:] if i >= flag] smaller = [i for i in arr[1:] if i < flag] return quick_sort(smaller) + [flag] + quick_sort(bigger)
快速排序的更优版本,不需要额外的数组空间,只需要递归空间
import java.util.Arrays; public class QuickSortDemo { public static void main(String[] args){ int[] src = {1,4,2,5,2,0,3,7,2,3,1}; QuickSort(src, 0, src.length-1); System.out.println("排序后: " + Arrays.toString(src)); } private static void QuickSort(int[] arr, int low, int high){ // Collections.shuffle(Arrays.asList(arr)); if(low>=high) return; // 先进行交换,使j左边的数都小于j,j右边的数都大于j,然后再左右递归 int j = Partition(arr, low, high); QuickSort(arr, low, j-1); QuickSort(arr, j+1, high); } private static int Partition(int[] arr, int low, int high){ int i = low, j = high + 1; int k = arr[low]; while(true){ while(arr[++i]<k) if(i==high) break; // 找左边大于等于k的数 while(arr[--j]>k) if(j==low) break; // 找右边小于等于k的数,j==low的时候循环条件一定为false if(i>=j) break; // 先判断是否已经相遇 int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } // 此时j右边的数都大于k,j左边的数包括j都小于k,交换位置j和k的值,进入下一轮划分 int temp = arr[low]; arr[low] = arr[j]; arr[j] = temp; return j; } }
public void quickSort(int[] nums, int left, int right){ // 插入排序+快速排序 if(right-left<=7){ for(int i=left+1; i<right+1; i++){ int temp = nums[i]; int j = i; while(j>0 && nums[j-1]>temp){ nums[j] = nums[j-1]; j--; } nums[j] = temp; } return; } int mid = partition(nums, left, right); quickSort(nums, left, mid-1); quickSort(nums, mid+1, right); } public int partition(int[] nums, int left, int right){ // 第二种划分方式,填坑法,要注意p和q的边界 int p = left, q = right; int base = nums[left]; while(p<q){ // 先在右边找比base小的填充base,再到左边找比base大的填充刚刚的小位置 while(p<q && nums[q]>=base){ q--; } // 要先判断p和q的边界,p再后面++就不用判断了 // if(p<q) nums[p++] = nums[q]; nums[p] = nums[q]; while(p<q && nums[p]<base){ p++; } // if(p<q) nums[q--] = nums[p]; nums[q] = nums[p]; } nums[q] = base; return q; } // 选择中间元素作为基准元素 public void quickSortMiddle(int[] nums, int left, int right){ int base = nums[(left+right)>>1]; int p = left, q = right; while(p<=q){ // 左边找比basic大的数 while(nums[p]<base){ p++; } // 右边找比basic小的数 while(nums[q]>base){ q--; } if(p<=q){ int temp = nums[p]; nums[p] = nums[q]; nums[q] = temp; p++; q--; } } if(left<q){ quickSortMiddle(nums, left, q); } if(p<right){ quickSortMiddle(nums, p, right); } }
三向切分的快速排序,把数组分为3段,当数组中有大量重复元素的时候,排序更快
import java.util.Arrays; public class QuickSort3Way{ public static void main(String[] args){ Character[] arrChar = {'R','B','W','W','R','W','W','B','X','R','W','B','R'}; Sort3Way(arrChar, 0, arrChar.length-1); System.out.println("排序之后: " + Arrays.toString(arrChar)); } private static <T extends Comparable<T>> void Sort3Way(T[] arr, int low, int high){ if(low>=high) return; T v = arr[low]; int p = low, i = low + 1, q = high; while(i<=q){ int flag = arr[i].compareTo(v); if(flag>0){ // 大于v的放右边 T temp = arr[i]; arr[i] = arr[q]; // 交换之后arr[i]和v大小是不确定的,所以i不动,arr[i]继续下一轮比较 arr[q--] = temp; } else if(flag<0){ // 小于v的放左边 T temp = arr[i]; arr[i++] = arr[p]; // 这时候的arr[i]和v是相等的,所以i++ arr[p++] = temp; } else{ // 等于v的放中间 i++; } } // 大于小于v的再递归 Sort3Way(arr, low, p-1); Sort3Way(arr, q+1, high); } }
5.归并排序
# 归并排序和快速排序一样,都是分而治之的思想,归并排序的时间复杂度是 O(nlog(n)),并且是一个稳定的排序算法 # 把一个数组递归分为对等两段,这两段放一起排序并且在排序过程中合并成一个数组 def mergeSort(arr): if len(arr) < 2: return arr middle = len(arr)//2 left = arr[:middle] right = arr[middle:] return merge(mergeSort(left), mergeSort(right)) # 两个数组的元素比较大小,合并成一个数组 def merge(left, right): mergeTwoArr = [] i = 0 j = 0 while i<len(left) and j<len(right): # 这里要小于总长度,而不是总长度减1;因为i是后加的,如果是总长度减1不能到最后一个元素 if(left[i]<=right[j]): mergeTwoArr.append(left[i]) i += 1 else: mergeTwoArr.append(right[j]) j += 1 mergeTwoArr += left[i:] # left或right总有一个下标超出了界限,得到一个空列表,没超出界限的那个比mergeTwoList里面的都大 mergeTwoArr += right[j:] return mergeTwoArr
归并,自顶向下和自底向上
import java.util.Arrays; public class MergeSortDemo { public static void main(String[] args){ int[] src2 = {1,4,2,5,2,0,3,7,2,3,1}; int[] aux = new int[src2.length]; MergeSortTB(src2, aux, 0, src2.length-1); System.out.println("排序后:" + Arrays.toString(src2)); int[] src3 = {1,4,2,5,2,0,3,7,2,3,1}; int[] aux2 = new int[src3.length]; MergeSortBU(src3, aux2); System.out.println("排序后:" + Arrays.toString(src3)); } // 自顶向下 private static void MergeSortTB(int[] arr, int[] aux, int low, int high){ if(low>=high) return; int mid = (low + high) >>> 1; MergeSortTB(arr, aux, low, mid); MergeSortTB(arr, aux, mid+1, high); if(arr[mid]<arr[mid+1]) return; Merge(arr, aux, low, mid, high); } // 自底向上,只需合并,不用递归 private static void MergeSortBU(int[] arr, int[] aux){ int N=arr.length; for(int sz=1; sz<N; sz+=sz) for(int low=0; low<N-sz; low += sz+sz) Merge(arr, aux, low, low+sz-1, Math.min(low+sz+sz-1, N-1)); } private static void Merge(int[] arr, int[] aux, int low, int mid, int high){ for(int i=low; i<=high; i++){ aux[i] = arr[i]; // 把左右两个小数组合并到辅助数组 } // 使用双指针对两个数组进行排序 for(int i=low, p=low, q=mid+1; i<=high; i++){ if(p>mid) arr[i] = aux[q++]; // 左边的数组排完了 else if(q>high) arr[i] = aux[p++]; // 右边的数组排完了 else if(aux[p]>aux[q]) arr[i] = aux[q++]; else arr[i] = aux[p++]; } } }
归并+插入(当归并到数组长度小于一个指定值时,对这小部分数使用插入排序)
import java.util.Arrays; public class MergeSortDemo { private static final int INSERTIONSORT_THRESHOLD = 7; public static void main(String[] args){ int[] src = {1,4,2,5,2,0,3,7,2,3,1}; int[] dest = src.clone(); MergeSort(src, dest, 0, src.length); System.out.println("排序后:" + Arrays.toString(src)); } // 归并+插入 public static void MergeSort(int[] src, int[] dest, int right, int left){ int len = left - right; // 写一个插入排序 if(len<INSERTIONSORT_THRESHOLD){ for(int i=right; i<left; i++){ for(int j=i; j>right && dest[j]<dest[j-1]; j--){ int temp = dest[j-1]; dest[j-1] = dest[j]; dest[j] = temp; } } return; } int mid = (right + left) >>> 1; MergeSort(src, dest, right, mid); MergeSort(src, dest, mid, left); // 递归+插入将左右两边的先各自排好,最后一轮视情况而定 // 如果右边的第一位大于左边的最后一位则直接返回 if(dest[mid]>dest[mid-1]){ System.arraycopy(dest, right, src, right, len); return; } for(int i=0, p=right, q=mid; i<left; i++){ // 使用双指针要注意“短路或”问题,对left的判断要放前面才会去判断后面的 // 如果q指针为true(超出界限), 说明右边处理完了, 接下来要处理p; // 如果p指针为false(超出界限), 说明左边处理完了, 接下来要处理q; if(q>=left || p<mid && dest[p]<=dest[q]){ // System.out.println(p+" "+q); src[i] = dest[p++]; } else{ // System.out.println(p+" "+q); src[i] = dest[q++]; } } } }
6.堆排序
O(nlogn)的时间复杂度,O(1)的空间复杂度
class Solution { public int[] sortArray(int[] nums) { // 堆排序(大根堆),原数组建堆 heapify(nums); // 不断交换堆顶元素和数组尾部的元素 for(int i=nums.length-1; i>0; i--){ swap(nums, 0, i); // 将堆顶元素下沉到合适位置,后面的不能动了,所以要传入一个界限 sink(nums, 0, i); } return nums; } public void heapify(int[] nums){ // 将数组转化为堆,把前半部分元素下沉到合适的位置 // 注意,要从中间的元素开始下沉,而不是从头部开始 int n = nums.length; for(int i=(n-1)/2; i>=0; i--){ sink(nums, i, n); } } public void sink(int[] nums, int idx, int end){ int j = 2 * idx + 1; while(j<end){ if(j+1<end && nums[j+1]>nums[j]) j++; if(nums[idx]<nums[j]) swap(nums, idx, j); else break; idx = j; j = 2 * idx + 1; } } public void swap(int[] nums, int i, int j){ int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } }