排序算法实现

本文详细介绍了选择排序、插入排序、冒泡排序、归并排序和快速排序这五种常见的排序算法,包括它们的基本原理、操作示例及时间复杂度分析。其中,选择排序和插入排序是简单直观的算法,冒泡排序是稳定的排序方法,归并排序和快速排序则是效率较高的排序算法。
摘要由CSDN通过智能技术生成

一般在面试笔试中,
最常考的排序算法是:快速排序、归并排序
常被提及的算法还有:插入排序、冒泡排序、堆排序、基数排序、桶排序


选择排序

选择排序是一种简单直观的排序算法,基本原理如下:

对于给定的一组记录,经过第一轮比后得到最小的记录,然后将该记录与第一个记录交换;接着对不包括第一个记录以外的其他记录进行第二轮的比较,得到最小的记录并与第二个记录进行位置交换;重复该过程,直到比较的记录只有一个时为止。

以数组{38, 65, 97, 76, 13, 27, 49}为例(假设要求为升序排列):
第一次排序后:13 [65 97 76 38 27 49]
第二次排序后:13 27 [97 76 38 65 49]
第三次排序后:13 27 38 [76 97 65 49]
第四次排序后:13 27 38 49 [97 65 76]
第五次排序后:13 27 38 49 65 [97 76]
第六次排序后:13 27 38 49 65 76 [97]
最后一次排序:13 27 38 49 65 76 97

具体代码实现:

class Solution:

    def select_sort(self, arr):
        """
        :param arr: 待排序的数组
        :return: 排序后的数组
        """
        if not arr:
            return []
        arr_len = len(arr)
        for i in range(arr_len-1):
            min_index = i
            for j in range(i+1, arr_len):
                if arr[j] < arr[min_index]:
                    min_index = j
            arr[i], arr[min_index] = arr[min_index], arr[i]
        return arr


if __name__ == "__main__":
    nums = [3, 4, 2, 8, 9, 5, 1]
    res = Solution().select_sort(nums)
    print(res)

选择排序是一种不稳定的排序,最好、最坏和平均情况下的时间复杂度都为 O(n2) ,空间复杂度为 O(1) .

插入排序

基本原理如下:

对于给定一组记录,初始时假设第一个记录自成一个有序序列,其余的记录为无序序列;接着从第二个记录开始,按照记录的大小依次将当前处理的记录插入到其之前的有序序列中,直到最后一个记录插入到有序序列中为止。

以数组{38, 65, 97, 76, 13, 27, 49}为例(假设要求为升序排列):
第一步插入38以后:[38] 65 97 76 13 27 49
第二步插入65以后:[38 65] 97 76 13 27 49
第三步插入97以后:[38 65 97] 76 13 27 49
第四步插入76以后:[38 65 76 97] 13 27 49
第五步插入13以后:[13 38 65 76 97] 27 49
第六步插入27以后:[13 27 38 65 76 97] 49
第七步插入49以后:[13 27 38 49 65 76 97]

具体代码实现:

class Solution:
    def insert_sort(self, arr):
        """
        :param arr: 待排序数组
        :return: 排序后的数组
        """
        if not arr:
            return []
        arr_len = len(arr)
        for i in range(1, arr_len):
            val = arr[i]
            j = i-1
            while j >= 0:
                if arr[j] > val:
                    arr[j+1] = arr[j]
                    arr[j] = val
                j -= 1
        return arr


if __name__ == "__main__":
    nums = [3, 4, 2, 8, 9, 5, 1]
    res = Solution().insert_sort(nums)
    print(res)

插入排序是一种稳定的排序方法,最好情况下的时间复杂度为O(n), 最坏情况下的时间复杂度为O(n2), 平均情况下的时间复杂度为O(n2)。空间复杂度为O(1).

冒泡排序

冒泡排序顾名思义就是整个过程就像气泡一样往上升,单向冒泡排序的基本原理是:

对于给定的n个记录,从第一个记录开始依次对相邻的两个记录进行比较,当前面的记录大于后面的记录时,交换其位置,进行一轮比较和换位后,n个记录中的最大记录将位于第n位;然后对前(n-1)个记录进行第二轮比较;重复该过程直到进行比较的记录只剩下一个为止。

以数组{36, 25, 48, 12, 25, 65, 43, 57}为例(假设要求为升序排列):
第一次排序:[25 36 12 25 48 43 57] 65
第二次排序:[25 12 25 36 43 48] 57 65
第三次排序:[12 25 25 36 43] 48 57 65
第四次排序:[12 25 25 36] 43 48 57 65
第五次排序:[12 25 25] 36 43 48 57 65
第六次排序:[12 25] 25 36 43 48 57 65
第七次排序:[12] 25 25 36 43 48 57 65

具体代码实现:

class Solution:
    def bubble_sort(self, arr):
        """
        :param arr: 待排序数组
        :return: 排序后的数组
        """
        arr_len = len(arr)
        for i in range(0, arr_len - 1):
            for j in range(arr_len-i):
                if arr[j] > arr[j+1]:
                    arr[j], arr[j+1] = arr[j+1], arr[j]
        return arr


if __name__ == "__main__":
    num = [3, 4, 2, 8, 9, 5, 1]
    res = Solution().bubble_sort(num)
    print(res)

冒泡排序是一种稳定的排序方法,最好的情况下的时间复杂度为O(n),最坏的情况下时间复杂度为O(n2),平均情况下时间复杂度为O(n2)。空间复杂度为O(1).

归并排序

归并排序是利用递归与分治技术将数据划分为越来越小的半子表,再对半子表排序,最后再用递归步骤将排好序的半子表合并为越来越大的有序序列。其中“归”代表的是递归的意思,即递归地将数组折半地分离为单个数组。具体而言,归并排序算法的原理如下:

对于给定的一组记录(假设共有n个记录),首先将每两个相邻长度为1的子序列进行归并,得到n/2(向上取整)个长度为2或1的有序子序列,再将其两两归并,反复执行此过程,直到得到一个有序序列为止。

所以归并排序的关键就是两步:第一步,划分子表;第二步,合并半子表。
以数组{49, 38, 65, 97, 76, 13, 27}为例(假设要求为升序排序),排序过程如下:
初始关键字:[49] [38] [65] [97] [76] [13] [27]
第一次归并:[38 49] [65 97] [13 76] [27]
第二次归并:[38 49 65 97] [13 27 76]
第三次归并:[13 27 38 49 65 76 97]

class Solution:
    def merge_sort(self, lists):
        if len(lists) <= 1:
            return lists
        num = len(lists) // 2
        left = self.merge_sort(lists[:num])
        right = self.merge_sort(lists[num:])
        return self.merge(left, right)

    def merge(self, left, right):
        i, j = 0, 0
        res = []
        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.extend(left[i:])
        res.extend(right[j:])
        return res


if __name__ == '__main__':
    arr = [3, 4, 2, 8, 9, 5, 1]
    res = Solution().merge_sort(arr)
    print(res)

二路归并排序的过程需要进行logN次。每一趟归并排序的操作,就是将两个有序子序列进行归并,而每一对有序子序列归并时,记录的比较次数均小于等于记录的移动次数,记录移动的次数均等于文件中记录的n个数,即每一趟归并的时间复杂度为O(n)。因此二路归并排序在最好、最坏和平均情况下的时间复杂度为O(nlogn),而且是一种稳定的排序方法,空间复杂度为O(1).

快速排序

快速排序是一种非常高效的排序算法,它采用“分而治之”的思想,把大的拆分成小的,小的再拆分成更小的。
其原理是:

对于一组给定的记录,通过一趟排序后,将原序列分为两部分,其中前部分的所有记录都比后部分的所有记录小,然后再依次对前后两部分的记录进行快速排序,递归该过程,直到序列中的所有记录均有序为止。

具体的算法步骤如下:

  1. 分解: 将输入的序列array[m,…,n]划分为两个非空子序列array[m,…,k]和array[k+1,…,n],使array[m,…,k]任意元素不大于array[k+1,…,n]中任一元素的值。
  2. 递归求解: 通过递归调用快速排序算法分别对array[m,…,k]和array[k+1,…,n]进行排序;
  3. 合并: 由于对分解出的两个子序列的排序是就地进行的,所以在array[m,…,k]和array[k+1,…,n]都排好序后,不需要执行任何计算,array[m,…,n]就已排好序。

以数组{49, 38, 65, 97, 76, 13, 27, 49}为例(假设要求为升序排序),排序过程如下:
整个排序过程如下:
初始化关键字排序:[49 38 65 97 76 13 27 49]
一次排序后:[27 38 13] 49 [76 97 65 49]
二次排序后:[13] 27 [38] 49 [49 65] 76 [97]
三次排序后:13 27 38 49 49 [65] 76 97
最后的排序结果:13 27 38 49 49 65 76 97

class Solution:
    def quick_sort(self, lists, left, right):
        if left >= right:
            return lists
        key = lists[left]
        low = left
        high = right
        while left < right:
            while left < right and lists[right] >= key:
                right -= 1
            lists[left] = lists[right]
            while left < right and lists[left] <= key:
                left += 1
            lists[right] = lists[left]
        lists[right] = key
        self.quick_sort(lists, low, left-1)
        self.quick_sort(lists, left+1, high)
        return lists

if __name__ == "__main__":
    lists = [3, 4, 2, 8, 9, 5, 1]
    print(Solution().quick_sort(lists, 0, len(lists)-1))

堆排序(JS实现)

参考:图解排序算法(三)之堆排序
堆是具有以下性质的完全二叉树:

  • 每个节点的值都大于或等于其左右孩子节点的值,称为大顶堆;
  • 每个节点的值都小于或等于其左右孩子节点的值,成为小顶堆;

堆排序的基本思想:

将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余(n-1)个元素重新构造成一个堆,这样会得到n个元素的此小值,便能得到一个有序序列了。

具体步骤:

  1. 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
  2. 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。

具体实现:

const heapSort = function(arr) {
    // 1. 构建大顶堆
    if(arr.length <= 1) return arr;
    let len = arr.length;
    for(let i=parseInt(arr.length/2-1);i>=0;i--){
        // 从第一个非叶子节点从下至上,从右至左调增结构
        adjustHeap(arr, i, len);
    }

    for(let j=len-1;j>0;j--){
        [arr[0], arr[j]] = [arr[j], arr[0]];
        adjustHeap(arr, 0, j);
    }

    return arr;
}

function adjustHeap(arr, i, len) {
    let temp = arr[i]; // 先取出当前元素i
    for(let k=2*i+1;k<len;k=2*i+1){
        if(k+1 < len && arr[k] < arr[k+1]) {
            k++;
        }
        if(arr[k] > temp) {
            arr[i] = arr[k];
            i = k;
        } else {
            break;
        }
    }
    arr[i] = temp;
}


// 测试
console.log(heapSort([9,8,7,6,5,4,3,2,1]));

堆排序是一种选择排序,整体主要由构建初始堆+交换堆顶元素和末尾元素并重建堆两部分组成。其中构建初始堆经推导复杂度为O(n),在交换并重建堆的过程中,需交换n-1次,而重建堆的过程中,根据完全二叉树的性质,[log2(n-1),log2(n-2)…1]逐步递减,近似为nlogn。所以堆排序时间复杂度一般认为就是O(nlogn)级

参考

未完待续…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值