• 十大排序算法的实现python&golang Marathon


    参考:

    对一个无序数组,根据某个关键字排序。

    划分方法
    排序算法划分方法有:稳定性内外排序时空复杂度

    按照稳定性划分,稳定排序,如果a原本在b前面,而a=b,排序之后a仍然在b的前面;而不稳定可能出现在b之后。

    按照内外排序划分,内排序,所有排序操作都在内存中完成;外排序 :由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;

    按照时空复杂度划分,时间复杂度是指运行时间,空间复杂度运行完一个程序所需内存的大小。

    1.冒泡

    思路:每一轮排序,每次循环从头开始比较,但尾部根据i变化,length-i-1或者length-i,相当于每次当前尾部位置固定,每次循环选出当前轮最大的放后面,大的往数组尾部移动,小的往前挪
    时空分析: 时间/空间 O(n^2)
    稳定性:稳定
    内排排序: 内排序

    python

    # 1.冒泡排序-稳定-内排序-时间复杂度O(n^2), 空间复杂度O(n^2)
    def bubbleSort(nums: [int]) -> [int]:
        
        for i in range(len(nums)):
            for j in range(1, len(nums)-i):
                if nums[j-1] > nums[j]:
                    nums[j-1], nums[j] = nums[j], nums[j-1]
        return nums
    

    golang

    // 1.bubble
    func bubbleSort(nums []int) []int {
    	
    	for i := range nums {
    		for j:=1;j<len(nums)-i;j++ {
    			if nums[j-1] > nums[j] {
    				nums[j-1], nums[j] = nums[j], nums[j-1]
    			}
    		}
    	}
    	return nums
    }
    

    2.选择

    思路:每一轮排序,每次循环从头开始比较,不断把小的往前面推,大的被选择交换到后面,每一轮选出当轮最小的,相当于i固定位置,但交换后的值肯定是当轮最小的
    时空分析: 时间/空间 O(n^2)
    稳定性:数组实现不稳定,链表稳定
    内排排序: 内排序

    python

    # 2.选择排序-不稳定-内排序-时空O(n^2)
    def selectSort(nums: [int]) -> [int]:
        length = len(nums)
        for i in range(length):
            for j in range(i, length):
                if nums[i] > nums[j]:
                    nums[i], nums[j] = nums[j], nums[i]
        return nums
    

    golang

    // 2.select
    func selectSort(nums []int) []int {
    	length := len(nums)
    	for i := range nums {
    		for j:=i;j<length;j++ {
    			if nums[i] > nums[j] {
    				nums[i], nums[j] = nums[j], nums[i]
    			}
    		}
    	}
    	return nums
    }
    

    3.插入

    思路:每一轮排序,会把当前遍历的元素之前的那些元素排序,使得每一轮循环结束,当前元素及其前面元素都排好序,后面再循环,相当于把某元素插入到合适位置
    e.g. nums = [1,2,3,2,6,4,7], 当前遍历的元素为下标3的元素2,后面的6/4/7不动,该元素2不断与其前面排好序的元素比较,直到找到下标2合适的插入位置,
    即下标1/2的元素2/3之间,所以此轮循环比较后,nums=[1,2,2,3,6,4,7],后面遍历重复此过程。
    时空分析: 时间/空间 O(n^2)
    稳定性:数组稳定
    内排排序: 内排序

    python

    # 3.插入排序-稳定-内排序-时空O(n^2)
    def insertSort(nums: [int]) -> [int]:
        length = len(nums)
        for i in range(1, length):
            while i > 0 and nums[i-1] > nums[i]:
                nums[i-1], nums[i] = nums[i], nums[i-1]
                i -= 1
        return nums
    

    golang

    // 3.insert
    func insertSort(nums []int) []int {
    	length := len(nums)
    	for i:=0;i<length;i++ {
    		for j:=i;j>0;j-- {
    			if nums[j-1] > nums[j] {
    				nums[j-1], nums[j] = nums[j], nums[j-1]
    			}
    		}
    	}
    	return nums
    }
    

    4.希尔-插入排序升级版

    思路:定义增量序列gap,通常取[1,2,4,...2^(k-1)], 循环增量序列,从大的增量开始递减,每个增量序列使用时,遍历从gap到length的元素,每次比较交换i-gapi位置的元素, 比较后i = i-gap,直到gap=0为止
    时空分析: 时间/空间 O(n^2), 与增量序列有关,[1,2,4,8...]最坏O(n^2), [1,3,5,7,9...]最坏O(n^1.5), [1,5,19,41,109,...] -> O(n^1.3),详情查阅《数据结构与算法分析》
    稳定性:不稳定
    内排排序: 内排序

    python

    # 4.希尔排序-不稳定-内排序-时空O(n^2)
    def shellSort(nums: [int]) -> [int]:
        length = len(nums)
        gap = length >> 1
        while gap:
            for i in range(gap, length):
                while i-gap >= 0 and nums[i-gap] > nums[i]:
                    nums[i-gap], nums[i] = nums[i], nums[i-gap]
                    i -= gap
            gap >>= 1
        return nums
    

    golang

    // 4.shell
    func shellSort(nums []int) []int {
    	length := len(nums)
    	gap := length >> 1
    	for gap > 0 {
    		for i:=gap;i<length;i++ {
    			j := i
    			for j-gap >= 0 && nums[j-gap] > nums[j] {
    				nums[j-gap], nums[j] = nums[j], nums[j-gap]
    				j -= gap
    			}
    		}
    		gap >>= 1
    	}
    	return nums
    }
    

    5.归并

    思路:递归实现,如length=10 的数组, 先拆分为前5后5的子数组,前5的子数组拆分为更小的子数组前2后3,前2的子数组拆分为更小的前1后1的子数组,合并left,right的元素各1的数组,返回,接下来处理下标为2-4的子数组,其实也是被拆分的排序后合并,一次递归出栈,最后合并前5元素的left和后5元素的right子数组,排序后合并,拆分的过程不断压栈,合并的过程不断出栈:

    • 将数组对半拆分为 n/2 的子数组
    • 对子数组进行排序
    • 合并两个子数组

    时空分析: 时间/空间 O(nlogn)
    稳定性:稳定
    内排排序: 内排序/外排序(解决数据量大的问题)

    python

    # 5.归并排序
    def mergeSort(nums: [int]) -> [int]:
    
        def merge(left: [int], right: [int]) -> [int]:
            res = []
            i, j = 0, 0
            while i < len(left) and j < len(right):
                if left[i] <= right[j]:
                    res.append(left[i])
                    i += 1
                else:
                    res.append(right[j])
                    j += 1
            res += left[i:]
            res += right[j:]
            return res
        
        # 递归终止条件
        if len(nums) <= 1:
            return nums
        mid = len(nums) >> 1
        # 分左右子数组
        left = mergeSort(nums[:mid])
        right = mergeSort(nums[mid:])
        # 合并
        return merge(left, right)
    

    golang

    // 5.merge
    func mergeSort(nums []int) []int {
    	// 递归终止
    	if len(nums) <= 1 {
    		return nums
    	}
    	// 拆分与合并
    	mid := len(nums) >> 1
    	left := mergeSort(nums[:mid])
    	right := mergeSort(nums[mid:])
    	return merge(left, right)
    }
    
    func merge(left, right []int) []int {
    	res := make([]int, len(left)+len(right))
    	l, r, i := 0, 0, 0
    	for l < len(left) && r < len(right) {
    		if left[l] <= right[r] {
    			res[i] = left[l]
    			l++
    		} else {
    			res[i] = right[r]
    			r++
    		}
    		i++
    	}
    	copy(res[i:], left[l:])
    	copy(res[i+len(left)-l:], right[r:])
    	return res
    }
    

    6.快速排序

    快速排序是选取一个“哨兵”(pivot),将小于pivot放在左边,把大于pivot放在右边,分割成两部分,并且可以固定pivot在数组的位置,在对左右两部分继续进行排序。

    快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。

    思路:

    • 步骤1:从数列中挑出一个元素,称为 “基准”(pivot );
    • 步骤2:重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
    • 步骤3:递归地(recursive)把小于基准值元素的子数组和大于基准值元素的子数组排序

    时空分析: 时间 O(nlogn)
    稳定性:不稳定
    内排排序: 内排序

    python

    # 6.快速排序-不稳定-内排序-时空O(nlogn)
    def quickSort(nums: [int]) -> [int]:
        length = len(nums)
    
        def quick(left: int, right: int) -> [int]:
            if left > right:
                return nums
            pivot = left
            i, j = left, right
            while i < j: # while循环的操作在于寻找pivot位置并交换元素,最终使得pivot为基准,分割左右半区元素,继续递归   
                while i < j and nums[j] >= nums[pivot]: # 大于pivot对应元素时, j--,实现右指针左移,直到 j <= i(往往相遇时相等) 或者 nums[j] < nums[pivot]
                    j -= 1
                while i < j and nums[i] <= nums[pivot]: # 小于等于pivot对应元素时,i++,实现左指针右移,直到 i >= j(往往相遇时相等) 或者 num[i] > nums[pivot]
                    i += 1
                nums[i], nums[j] = nums[j], nums[i] # 当 i=j 时,已经跳出上面两个while循环,即左右指针相遇,或者(j的元素小于等于pivot && i的元素大于pivot),肯定需要交换不同位置元素,实现pivot分割数组,最终使得左右指针相遇
            nums[pivot], nums[j] = nums[j], nums[pivot] # 交换基准与左右指针相遇位置的元素,此次分区结束,新排序的数组一定是pivot元素大于左区,小于右区
            quick(left, j-1) # 递归左半区
            quick(j+1, right) # 递归右半区
            return nums
    
        return quick(0, length-1)
    

    golang

    // 6.quick
    func quickSort(nums []int) []int {
    	length := len(nums)
    	var quick func(nums []int, left, right int) []int
    	quick = func(nums []int, left, right int) []int {
    		if left > right {return nil}
    		i, j, pivot := left, right, nums[left]
    		for i < j {
    			for i < j && nums[j] >= pivot {
    				j--
    			}
    			for i < j && nums[i] <= pivot {
    				i++
    			}
    			nums[i], nums[j] = nums[j], nums[i]
    		}
    		nums[i], nums[left] = nums[left], nums[i]
    		quick(nums, left, i-1)
    		quick(nums, i+1, right)
    		return nums
    	}
    	return quick(nums, 0, length-1)
    }
    

    7.计数排序

    计数排序是典型的空间换时间算法,开辟额外数据空间存储用索引号记录数组的值和数组值个数

    思路:

    • 找出待排序的数组的最大值和最小值
    • 创建数组存放各元素出现次数,限于[min, max]之间的数
    • 统计数组值的个数
    • 反向填充目标数组,填充的时候注意,nums[i] = j + min,j表示前面需要略过的数的个数,两个维度,一个是依次递增的数(j++),一个是重复的数的计数(j不变)

    时空分析: 时间 O(n+k),数据量大时,花费时间与空间量大
    稳定性:稳定
    内排排序: 外排序

    python

    # 7.计数排序
    def countSort(nums: [int]) -> [int]:
        if not nums: return []
        length = len(nums)
        Min = min(nums)
        Max = max(nums)
        # 创建基于最值的有限数组并初始化
        tempArr = [0] * (Max-Min+1)
        # 实现计数, >0代表下标数出现次数, 如果0比较多,表示[min, max]之间的nums数组元素,某些是没有的,即空间换时间
        for num in nums:
            tempArr[num-Min] += 1
    
        j = 0 # 初始化
        for i in range(length):
            while tempArr[j] == 0: # 等于0表示次数减完或者需要跳过,自然就需要j++
                j += 1
            nums[i] = j + Min # 替换排序的值
            tempArr[j] -= 1 # 出现次数--
    
        return nums
    

    golang

    // 7.count
    func countSort(nums []int) []int {
    	if len(nums) == 0 {return []int{}}
    	length := len(nums)
    	min_ := func(arr []int) int {
    		minNum := math.MaxInt32
    		for _, val := range arr {
    			if val <= minNum {
    				minNum = val
    			}
    		}
    		return minNum
    	} (nums)
    	max_ := func(arr []int) int {
    		maxNum := math.MinInt32
    		for _, val := range arr {
    			if val >= maxNum {
    				maxNum = val
    			}
    		}
    		return maxNum
    	} (nums)
    	tempArr := make([]int, max_-min_+1)
    	for _, val := range nums {
    		tempArr[val-min_]++
    	}
    	j := 0
    	for i:=0;i<length;i++ {
    		for tempArr[j] == 0 {
    			j++
    		}
    		nums[i] = j + min_
    		tempArr[j]--
    	}
    	return nums
    }
    

    8.桶排序

    桶排序是计数排序的升级版,原理是:输入数据服从均匀分布的,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的算法或是以递归方式继续使用桶排序,此文编码采用递归方式)

    算法描述:

    • 人为设置一个桶的BucketSize,作为每个桶放置多少个不同数值(意思就是BucketSize = 5,可以放5个不同数字比如[1, 2, 3,4,5]也可以放 100000个3,只是表示该桶能存几个不同的数值)
    • 遍历待排序数据,并且把数据一个一个放到对应的桶里去
    • 对每个不是桶进行排序,可以使用其他排序方法,也递归排序
    • 不是空的桶里数据拼接起来

    思路:

    时空分析: 时间 O(n+k)
    稳定性:稳定
    内排排序: 外排序

    python

    # 8.桶排序
    def bucketSort(nums: [int]) -> [int]:
    
        def bucket_sort(nums: [int], bucketSize: int) -> [int]:
            if len(nums) < 2:
                return nums
            min_ = min(nums)
            max_ = max(nums)
            # 需要桶的数量
            bucketNum = (max_-min_) // bucketSize + 1
            buckets = [[] for _ in range(bucketNum)]
            for num in nums:
                # 放入对应桶中
                buckets[(num-min_) // bucketSize].append(num)
            res = []
            for bucket in buckets:
                if not bucket: continue
                if bucketSize == 1:
                    res.extend(bucket)
                else:
                    # 全部放一个桶,说明容量大了
                    if bucketNum == 1:
                        bucketSize -= 1
                    res.extend(bucket_sort(bucket, bucketSize))
            return res
    
        bucketSize = 5
        return bucket_sort(nums, bucketSize)
    

    golang

    // 8.bucket
    func bucketSort(nums []int) []int {
    	var bucket_sort func(nums []int, bucketSize int) []int
    	bucket_sort = func(nums []int, bucketSize int) []int {
    		if len(nums) < 2 {
    			return nums
    		}
    		min_ := func(arr []int) int {
    			minNum := math.MaxInt32
    			for _, val := range arr {
    				if val <= minNum {
    					minNum = val
    				}
    			}
    			return minNum
    		} (nums)
    		max_ := func(arr []int) int {
    		maxNum := math.MinInt32
    		for _, val := range arr {
    			if val >= maxNum {
    				maxNum = val
    			}
    		}
    		return maxNum
    	} (nums)
    
    		buckets := make([][]int, bucketSize)
    
    		for j:=0;j<len(nums);j++ {
    			n := (nums[j]-min_) / ((max_-min_+1)/bucketSize)
    			buckets[n] = append(buckets[n], nums[j])
    			k := len(buckets[n]) - 2
    			for k>=0 && nums[j] < buckets[n][k] {
    				buckets[n][k+1] = buckets[n][k]
    				k--
    			}
    			buckets[n][k+1] = nums[j]
    		}
    		j := 0
    		for p, q := range buckets {
    			for t:=0;t<len(q);t++ {
    				nums[j] = buckets[p][t]
    				j++
    			}
    		}
    		return nums
    	}
    
    	var bucketSize int = 5
    	return bucket_sort(nums, bucketSize)
    }
    

    9.基数排序

    思路:
    基数排序是对数字每一位进行排序,从最低位开始排序

    算法描述:

    • 找到数组最大值,得最大位数;
    • 从最低位开始取每个位组成radix数组;
    • 对radix进行计数排序(计数排序适用于小范围的特点)。

    时空分析: 时间 O(n*k)
    稳定性:稳定
    内排排序: 外排序

    python

    # 9.基数排序
    def radixSort(nums: [int]) -> [int]:
        if not nums: return []
        max_ = max(nums)
        # 最大位数
        maxDigit = len(str(max_))
        bucketList = [[] for _ in range(10)]
        # 从低位开始
        div, mod = 1, 10
        for i in range(maxDigit):
            for num in nums:
                bucketList[num % mod // div].append(num)
            div *= 10
            mod *= 10
            index = 0
            for j in range(10):
                for item in bucketList[j]:
                    nums[index] = item
                    index += 1
                bucketList[j] = []
        return nums
    

    golang

    待完善
    

    10.堆排序

    思路:
    堆排序是利用堆这个数据结构设计的排序算法。

    算法描述:

    • 建堆,从底向上调整堆,使得父亲节点比孩子节点值大,构成大顶堆;
    • 交换堆顶和最后一个元素,重新调整堆。

    时空分析: 时间 O(nlogn)
    稳定性:不稳定
    内排排序: 内排序

    python

    # 10.堆排序-不稳定-内排序-时间复杂度O(nlogn)
    def heapSort(nums: [int]) -> [int]:
        # 调整堆
        # 迭代法
        def adjustHeap(nums, startPos, endPos):
            newItem = nums[startPos]
            pos = startPos
            childPos = pos*2 + 1
            while childPos < endPos:
                rightPos = childPos + 1
                if rightPos < endPos and nums[rightPos] >= nums[childPos]:
                    childPos = rightPos
                if newItem < nums[childPos]:
                    nums[pos] = nums[childPos]
                    pos = childPos
                    childPos = pos*2 + 1
                else:
                    break
            nums[pos] = newItem
    
        # 递归法
        def adjustHeap_(nums, startPos, endPos):
            pos = startPos
            childPos = pos*2 + 1
            if childPos < endPos:
                rightPos = childPos + 1
                if rightPos < endPos and nums[rightPos] > nums[childPos]:
                    childPos = rightPos
                if nums[childPos] > nums[pos]:
                    nums[pos], nums[childPos] = nums[childPos], nums[pos]
                    adjustHeap_(nums, pos, endPos)
    
        length = len(nums)
        # 1.建堆
        for i in reversed(range(length//2)):
            adjustHeap(nums, i, length)
        # 2.调整堆
        for i in range(length-1, -1, -1):
            nums[0], nums[i] = nums[i], nums[0]
            adjustHeap(nums, 0, i)
        return nums
    

    golang

    // 10.heap
    func heapSort(nums []int) []int {
    	end := len(nums) - 1
    	for i:=end/2;i>=0;i-- {
    		sink(nums, i, end)
    	}
    	for i:=end;i>=0;i-- {
    		nums[0], nums[i] = nums[i], nums[0]
    		end--
    		sink(nums, 0, end)
    	}
    	return nums
    }
    
    // 堆排-建堆
    func sink(nums []int, root, end int)  {
    	for {
    		child := root * 2 + 1
    		if child > end {
    			return
    		}
    		if child < end && nums[child] <= nums[child+1] {
    			child++
    		}
    		if nums[root] > nums[child] {
    			return
    		}
    		nums[root], nums[child] = nums[child], nums[root]
    		root = child
    	}
    }
    
  • 相关阅读:
    yocto添加层简介
    ARM Linux 3.x的设备树(Device Tree)
    Linux device tree 简要笔记
    git 分支( branch ) 的基本使用
    Git 常用命令速查表(三)
    Git 常用命令详解(二)
    CentOS Linux安装python3
    R语言统计学习-1简介
    cnblog中添加数学公式支持
    我们数学中常用的自然常数e代表什么?看完长知识了!
  • 原文地址:https://www.cnblogs.com/davis12/p/15652473.html
Copyright © 2020-2023  润新知