排序算法

232 阅读2分钟

几种常见的排序算法

快速排序

基本思想

  1. 设定一个基准数,把小于基准数的数放到基准数前,把大于基准数的数放在基准数后
  2. 对基准数前的子数组递归排序,对基准数后的子数组递归排序

代码实现时,设置两个哨兵i和j,分别从left和right出动。注意要让j先出动,因为只有这样当i和j相遇时,说明是j先到达的,也就是arr[i]=arr[j]<pivot,这时交换arr[left]和arr[i]才能保证交换后基准左边的数都小于基准

代码实现

class Sort{
	public void quickSort(int[] arr,int left,int right) {
		if (left>right) {
			return;
		}
		int pivot=arr[left];//pivot存的就是基准数
		int i=left;
		int j=right;
		while (i!=j) {
			//从右往左找小于基准数的数
			while (arr[j]>=pivot && i<j) {
				j--;
			}
			//从左往右找大于基准数的数
			while (arr[i]<=pivot && i<j) {
				i++;
			}
			if (i<j) {
				int temp=arr[i];
				arr[i]=arr[j];
				arr[j]=temp;
			}
		}
        //当i遇到j的时候,说明i(j)左边的数都小于arr[i]了,i右边的数都大于arr[i]了
		arr[left]=arr[i];
		arr[i]=pivot;
		this.quickSort(arr, left, i-1);//递归出理基准左边的
		this.quickSort(arr, i+1, right);//递归处理基准右边的
	}
}

堆排序

图解

基本思想

  1. 根据初始序列构建初始(大顶)堆,保证父节点都比它的孩子节点数值大
  2. 每次交换堆的第一个元素(根)和最后一个元素,输出最后一个元素(最大值),然后把剩下元素(除去最后一个元素)重新调整为大顶堆

代码实现

def HeadSort(input_list):
    '''
    Function:堆排序(升序)
    Parameters:input_list-待排序列表
    Returns:sorted_list-升序排列的列表
    '''
    def HeadAdjust(input_list, parent, length):
        '''
        Function:堆调整,调整为大顶堆
        Parameters:
            input_list-待排序列表
            parent-堆的父节点
            length-数组长度
        Returns:无
        '''
        temp = input_list[parent]
        child = 2*parent+1
        while child < length:
            if child+1<length and input_list[child]<input_list[child+1]:#找孩子节点中较大值
                child+=1
            if temp>=input_list[child]:
                break
            input_list[parent]=input_list[child]
            parent=child#往下查看是否需要调整
            child=2*parent+1
        input_list[parent]=temp
    if len(input_list)==0:
        return []
    sorted_list=input_list
    length=len(sorted_list)
    for i in range(0,length//2)[::-1]:#构造初始堆
        HeadAdjust(sorted_list,i,length)
    print('初始堆:',sorted_list)
    for j in range(1,length)[::-1]:#堆调整
        temp=sorted_list[j]
        sorted_list[j]=sorted_list[0]
        sorted_list[0]=temp
        HeadAdjust(sorted_list,0,j)
        print('第%d趟排序:'%(length-j),end=' ')
        print(sorted_list)
    return sorted_list

input_list=[1,3,4,5,2,6,9,7,8,0]
print('排序前:',input_list)
sorted_list=HeadSort(input_list)
print('排序后:',sorted_list)

归并排序

基本思想

归并其实是分治的思想。

通过递归实现链表归并排序,有以下两个环节:

  • 分割cut环节:找到当前链表中点,并从中点将链表断开
    • 使用快慢指针法,fast到链表尾时,slow在链表中点
    • cut递归终止条件:只有一个节点,直接返回此节点
  • 合并merge环节:将两个有序链表合并,转化为一个有序链表
    • 建立辅助节点h作为头部
    • 双指针分别指向两个有序链表头部,交替前进,直至添加完两个链表
    • 返回辅助链表h的下个节点

代码实现

排序链表

class Solution {
    public ListNode sortList(ListNode head) {
    	if (head==null) {
    		return null;
    	}
    	if (head.next==null) {
    		return head;
    	}
    	ListNode slow=head;
    	ListNode fast=head.next;
    	while (fast!=null && fast.next!=null) {
    		slow=slow.next;
    		fast=fast.next.next;
    	}
    	ListNode left=head;
    	ListNode right=slow.next;
    	slow.next=null;
    	left=sortList(left);
    	right=sortList(right);
    	ListNode i=left;
    	ListNode j=right;
    	ListNode h=new ListNode();
    	ListNode cur=h;
    	while (i!=null && j!=null) {
    		if (i.val<j.val) {
    			cur.next=i;
    			ListNode t=i.next;
    			i.next=null;
    			i=t;
    		}else {
    			cur.next=j;
    			ListNode t=j.next;
    			j.next=null;
    			j=t;
    		}
    		cur=cur.next;
    	}
    	if (i!=null) {
    		cur.next=i;
    	}
    	if (j!=null) {
    		cur.next=j;
    	}
    	return h.next;

    }
}

数组排序

		public void mergeSort(int[] nums,int left,int right) {
		//对nums从索引left到right进行排序
		if (left==right) {
			return;
		}
		int mid=(left+right)/2;
		mergeSort(nums, left, mid);
		mergeSort(nums, mid+1, right);
		int[] res=new int[right-left+1];
		int p1=left,p2=mid+1;
		int p=0;
		while (p1<=mid || p2<=right) {
			if (p1>mid) {
				res[p++]=nums[p2++];
			}else if (p2>right) {
				res[p++]=nums[p1++];
			}else {
				if (nums[p1]<nums[p2]) {
					res[p++]=nums[p1++];
				}else {
					res[p++]=nums[p2++];
				}
			}
		}
		for (int i=0;i<res.length;i++) {
			nums[left+i]=res[i];
		}
		
	}

493. 翻转对 - 力扣(LeetCode) (leetcode-cn.com)

327. 区间和的个数 - 力扣(LeetCode) (leetcode-cn.com)

基数排序

基本思想

假设原来一串数值如下所示:

72,22,93,43,55,14,65,39,81

首先根据个位数的数值,在走访数值时将它们分配至编号0到9的桶子中:

分配过程:

  1. 设置长度为10数组cnt,cnt[i]表示这串数值中个位数为i的数的个数,cnt={0,1,2,2,1,2,0,0,0,1},个位数为1的有一个(81),个位数为2的有两个(72、22)
  2. 对cnt元素进行累加赋值,cnt[i]+=cnt[i-1],cnt={0,1,3,5,6,8,8,8,8,9},这样cnt[i]就表示个位为i的元素应该排序后的位置
  3. 对数组按照个位数大小按序放置buf={81,72,22,93,43,14,55,65,39}

接着再对buf按照十位排序,如果数字串中有三位数,需要对百位排序

代码实现

def radixSort(nums:List):
    length=len(nums)
    exp=1
    maxVal=max(nums)
    buf=[0 for i in range(length)]
    while maxVal>=exp:
        cnt=[0 for i in range(10)]
        for i in range(length):
            digit=nums[i]//exp%10
            cnt[digit]+=1
        for i in range(1,10):
            cnt[i]+=cnt[i-1]
        //注意这里要从后往前放置,比如对10位排序时
        //十位相同的数,个位大的要放在后面
        //cnt[digit]是递减的,也就是要先放个位大的,所以要从后往前放
        for i in range(length-1,-1,-1):
            digit=nums[i]//exp%10
            buf[cnt[digit]-1]=nums[i]
            cnt[digit]-=1
        nums=[x for x in buf]
        exp*=10
    return nums