排序算法

 

 

leetcode 总结

 

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])
View Code

 

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
View Code

二分插入排序(查找的时候使用二分查找,而不是逐位向前查找)

希尔排序

希尔排序克服了插入排序中当最小的数在最右边的时候,需要把这个数逐位移到第一位的弊端;

希尔排序引入了增量序列,对每个增量序列下的子数组进行大间隔插入排序,随着后面的间隔越来越小,数组逐渐趋于部分有序

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;
                }
        */
    }
}
View Code

 

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
View Code

 

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)
View Code

快速排序的更优版本,不需要额外的数组空间,只需要递归空间

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);
    }
}
View Code

 

 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
View Code

归并,自顶向下和自底向上

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++];
        }
    }
}    
java归并

归并+插入(当归并到数组长度小于一个指定值时,对这小部分数使用插入排序)

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++];
            }
        }
    }
}
View Code

 

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;
    }

}    
堆排序

 

posted @ 2021-09-08 21:17  菠萝机  阅读(25)  评论(0编辑  收藏  举报