Task 03:数组排序(day3)

Task 03: 数组排序

第6天打卡,附上学习链接

1 学习内容

1.1 快速排序(Quick Sort)

通过一趟排序将无序序列分为独立的两个序列,第一个序列的值均比第二个序列的值小,然后递归地排序两个子序列,以达到整个序列有序。

算法步骤:

  • (1)从数组中找到一个基准数;

  • (2)然后将数组中比基数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧,从而将数组拆分为左右两个部分;

  • (3)再对左右两个部分分别重复第二步,直到各个部分只有一个数,则排序结束。

算法分析:

  • 在参加排序的元素初始时已经有序的情况下,快速排序方法花费的时间最长。此时,第1趟经过n-1次比较之后,确定第1个元素仍然在原来的位置上,并得到1个长度为n-1的子序列;第2趟排序经过n-2次比较后,确定第2个元素在它原来的位置上,又得到1个长度为n-2的子序列,类推得到最终的总比较次数为n-1+n-2+...+1=n(n-1)/2,因此时间复杂度为O(n2);

  • 还有一种情况,每趟排序后,分界元素正好定位在序列的中间,从而把当前参加排序的序列分成大小相等的前后两个子序列,而对长度为n的序列进行快速排序所需要的时间为T(n)<=n+2T(n/2)<=2n+4T(n/4)<=3n+8T(n/8)...<=(log2n),因此快速排序方法的时间复杂度为O(nlog2n),时间性能显然优于之前的几种排序算法。

  • 无论快速排序算法递归与否,排序过程中都需要用到堆栈或其他结构的辅助空间来存放当前待排序序列的首、尾位置。最坏的情况下,空间复杂度为O(n);

  • 进一步改进,在一趟排序之后比较被划分所得到的两个子序列的长度,先对长度较短的子序列进行快速排序,此时空间复杂度可以达到O(log2n);

  • 快速排序是一种不稳定排序算法,也是一种不适合在链表结构上实现的排序算法。

代码实现:

import random

def randomPartition(arr: [int], low: int, high: int):
    i = random.randint(low, high)
    arr[i], arr[high] = arr[high], arr[i]
    return partition(arr, low, high)

def partition(arr: [int], low: int, high: int):
    x = arr[high]
    i = low - 1
    for j in range(low, high):
        if arr[j] <= arr[high]:
            i += 1
            arr[i], arr[j] = arr[j], arr[i]
    arr[i+1], arr[high] = arr[high], arr[i+1]
    return i+1

def quickSort(arr, low, high):
    if low < high:
        pi = randomPartition(arr, low, high)
        quickSort(arr, low, pi-1)
        quickSort(arr, pi+1, high)
        
     return arr
1.2 堆排序(Heap Sort)

将数组转化为大顶堆,重复从大顶堆中取出数值最大的节点,并让剩余的堆维持大顶堆性质。

堆的定义:堆是符合以下两个条件之一的完全二叉树,大顶堆即根节点值>=子节点值;小顶堆即根节点值<=子节点值。

算法步骤(主要涉及初始堆建立方法和堆调整方法):

  • (1)首先将无序序列构造成第1个大顶堆(初始堆),使得n个元素的最大值处于序列的第1个位置;

  • (2)然后交换序列的第1个元素(最大值元素)与最后一个元素的位置;

  • (3)此后,再把序列的前n-1个元素组成的子序列调整成一个新的大顶堆,这样得到第2个最大值元素,把子序列的第1个元素(最大值元素)与第n-1个元素交换位置;

  • (4)此后再把序列的前n-2个元素调整成一个新的大顶堆,...,如此下去,直到整个序列变换成一个有序序列。

初始堆建立方法:

  • (1)如果原始序列对应的完全二叉树(不一定是堆)的深度为d,则从d-1层最右侧分支节点(序号为floor(n/2))开始,初始时令i=floor(n/2),调用堆调整算法;

  • (2)每调用一次堆调整算法,执行一次i=i-1,直到i==1时,再调用一次,就把原始序列调整为了一个初始堆。

堆调整方法:

  • 即把移走了最大值元素以后的剩余元素的序列再构造为一个新的堆积。

  • (1)从根节点开始,自上而下地调整节点的位置,使其成为堆积。即把序号为i的节点与其左子树节点(序号为2i)、右子树节点(序号为2i+1)中值最大的节点交换位置;

  • (2)因为交换了位置,使得当前节点的左右子树原有的堆积特性被破坏。于是从当前节点的左右子树节点开始,自上而下地继续进行类似的调整;

  • (3)如此下去直到整颗完全二叉树成为一个大顶堆。

算法分析:

  • 堆排序的时间主要花费在将原始序列构造为一个初始堆和不断调整堆两方面;

  • 设原始序列所对应的完全二叉树深度为d,算法由两个独立的循环组成:

  • (1)在第1个循环构造初始堆时,从i=d-1层开始,到i=1层为止,对每个分支节点都要调用一次adjust算法,每一次adjust算法,对于第i层一个节点到第d层上建立的子堆积,所有节点可能移动的最大距离为该子堆根节点移动到最后一层(第d层)的距离即d-i;

  • (2)第i层上节点最多有2i-1个,所以每次adjust算法最大移动距离为2i-1*(d-i)。所以堆排序的第1个循环所需时间应该是各层上的节点数与该层上节点可移动的最大距离之积的总和,时间复杂度为O(n)(原谅我没看懂);

  • (3)第2个循环中每次调用adjust算法一次,节点移动的最大距离为这棵完全二叉树的深度d=floor(log2n)+1,一共调用了n-1次adjust算法,此循环的时间复杂度为(n-1)(floor(log2n)+1)=O(nlog2n);

  • (4)综合,堆排序的时间复杂度为O(nlog2n);

  • 堆排序需要一个记录大小的辅助空间,所以堆排序的空间复杂度为O(1)。

  • 堆排序属于不稳定排序算法,也是一种不适合在链表上实现的排序。

代码实现:

# 调整为大顶堆
def heapify(arr: [int], index: int, end: int):
   left = index * 2 + 1
   right = left + 1
   while left <= end:
       # 当前节点为非叶子结点
       max_index = index
       if arr[left] > arr[max_index]:
           max_index = left
       if right <= end and arr[right] > arr[max_index]:
           max_index = right
       if index == max_index:
           # 如果不用交换,则说明已经交换结束
           break
       arr[index], arr[max_index] = arr[max_index], arr[index]
       # 继续调整子树
       index = max_index
       left = index * 2 + 1
       right = left + 1

# 初始化大顶堆
def buildMaxHeap(arr: [int]):
   size = len(arr)
   # (size-2) // 2 是最后一个非叶节点,叶节点不用调整
   for i in range((size - 2) // 2, -1, -1):
       heapify(arr, i, size - 1)
   return arr

# 升序堆排序,思路如下:
# 1. 先建立大顶堆
# 2. 让堆顶最大元素与最后一个交换,然后调整第一个元素到倒数第二个元素,这一步获取最大值
# 3. 再交换堆顶元素与倒数第二个元素,然后调整第一个元素到倒数第三个元素,这一步获取第二大值
# 4. 以此类推,直到最后一个元素交换之后完毕。
def maxHeapSort(arr: [int]):
   buildMaxHeap(arr)
   size = len(arr)
   for i in range(size):
       arr[0], arr[size-i-1] = arr[size-i-1], arr[0]
       heapify(arr, 0, size-i-2)
   return arr

2 练习题目

2.1 0075 颜色分类 ** (经典的「荷兰国旗问题」)

题目描述:给定一个包含红色、白色和蓝色,一共n个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按红色、白色、蓝色顺序排列,此处使用整数0、1和2表示红、白和蓝。 样例1:输入为nums=[2, 0, 2, 1, 1, 0],输出为[0, 0, 1, 1, 2, 2]; 样例2:输入为nums=[2, 0, 1],输出为[0, 1, 2]; 样例3:输入为nums=[0],输出为[0]; 样例4:输入为nums=[1],输出为[1]。

解题思路1:单指针。用一个指针ptr表示头部,代表数组nums从位置0到位置ptr-1都属于头部。第一次遍历,找到0,就与头部位置的元素交换,并往后扩充一个位置;第二次遍历,同理找到1。

class Solution:
    def sortColors(self, nums: List[int]) -> None:
        n = len(nums)
        ptr = 0
        for i in range(n):
            if nums[i] == 0:
                nums[i], nums[ptr] = nums[ptr], nums[i]
                ptr += 1
        for i in range(ptr, n):
            if nums[i] == 1:
                nums[i], nums[ptr] = nums[ptr], nums[i]
                ptr += 1

时间复杂度:O(n); 空间复杂度:O(1)。

解题思路2:双指针。一次遍历,p0指针用来交换0,p1指针用来交换1。

class Solution:
    def sortColors(self, nums: List[int]) -> None:
        n = len(nums)
        p0 = p1 = 0
        for i in range(n):
            if nums[i] == p1:
                nums[i], nums[p1] = nums[p1], nums[i]
                p1 += 1
            elif nums[i] == 0:
                nums[i], nums[p0] = nums[p0], nums[i]
                if p0 < p1:
                    nums[i], nums[p1] = nums[p1], nums[i]
                p0 += 1
                p1 += 1

时间复杂度:O(n); 空间复杂度:O(1)。

解题思路3:双指针。p0指针用来交换0到首部,初始值为0,从左向右遍历;p2指针用来交换2到尾部,初始值为n-1,从右向左遍历。

class Solution:
    def sortColors(self, nums: List[int]) -> None:
        n = len(nums)
        p0, p2 = 0, n-1
        i = 0
        while i <= p2:
            while i <= p2 and nums[i] == 2:
                nums[i], nums[p2] = nums[p2], nums[i]
                p2 -= 1
            if nums[i] == 0:
                nums[i], nums[p0] = nums[p0], nums[i]
                p0 += 1
            i += 1

时间复杂度:O(n); 空间复杂度:O(1)。

2.2 0215 数组中的第K个最大元素 **

题目描述:给定整数数组nums和整数k,返回数组中第k个最大的元素。 样例1:输入为[3, 2, 1, 5, 6, 4], k=2,输出5; 样例2:输入为[3, 2, 3, 1, 2, 4, 5, 5, 6],k=4,输出为4。

解题思路:乱,整理一下。

class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        def partition(nums, left, right):
            pivot = nums[left]
            i, j = left, right
            while i < j:
                while i < j and nums[j] >= pivot:
                    j -= 1
                nums[i] = nums[j]
                while i < j and nums[i] <= pivot:
                    i += 1
                nums[j] = nums[i]
            nums[i] = pivot
            return i
        
        def topk_split(nums, k, left, right):
            if left < right:
                index = partition(nums, left, right)
                if index == k:
                    return
                elif index < k:
                    topk_split(nums, k, index+1, right)
                else:
                    topk_split(nums, k, left, index-1)
                    
        def topk_large(nums, k):
            topk_split(nums, len(nums)-k, 0, len(nums)-1)
            return nums[len(nums)-k]
        
        return topk_large(nums, k)
2.3 剑指Offer 40 最小的k个数 *

题目描述:输入整数数组arr,找出其中最小的k个数。 样例1:输入为arr=[3, 2, 1], k=2,输出为[1, 2]或[2, 1]; 样例2:输入为arr=[0, 1, 2, 1], k=1,输出为[0]。

解题思路:排序,去前k个数即可。

class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        arr.sort()
        return arr[:k]
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容