赞
踩
插入排序
每一步将一个待排序的元素,按照其排序码的大小,插入到前面已经排好序的一组元素的合适位置上去,直到元素全部插完为止。
当插入第i
(i>=1)
个元素时,前面的array[0],array[1],…,array[i-1]
已经排好序,此时用array[i]
的排序码与array[i-1],array[i-2],…
的排序码顺序进行比较,找到插入位置即将array[i]
插入,原来位置上的元素顺序后移
数组的插入排序:
vector<int> sortArray(vector<int>& nums) { int len = nums.size(); //从第二个数字开始 往前插入数字 for(int i = 1; i < len; i++) { int cur = nums[i]; int j = i - 1; //寻找插入位置的过程中 不断的将比cur大的数字往后挪 while(j >= 0 && cur < nums[j]) { nums[j + 1] = nums[j]; //元素后移 j--; } //两种情况跳出循环: //(1)遇到一个小于或者等于cur的数字 跳出循环 cur就插入到它前面 //(2)已经走到数组头 仍然找不到 此时j == -1 cur 就插入到数组头部 nums[j + 1] = cur; } return nums; }
单链表的插入排序:
1.首先判断给定的链表是否为空,若为空,则不需要进行排序,直接返回
2.创建哑结点
3.维护lastSorted
为链表的已排序部分的最后一个结点,初始时lastSorted=head
4.维护cur
为待插入的元素,初始时cur = cur->next
;
5.比较lastSorted
和curr
的结点值
lastSorted.val <= cur.val
,说明cur
应该在lastSorted
之后,将lastSorted
后移一位,cur
变成心的lastSorted
.lastSorted->next = cur->next;
cur->next = pre->next;
pre->next = cur;
6.令cur = lastSorted->next,此时cur为下一个待插入的元素.
7.重复步骤5和6,直到cur为空
8.返回dummy->next;
class Solution { public: ListNode* insertionSortList(ListNode* head) { if (head == nullptr) { return head; } ListNode* dummyHead = new ListNode(0); dummyHead->next = head; ListNode* lastSorted = head; ListNode* curr = head->next; while (curr != nullptr) { if (lastSorted->val <= curr->val) { lastSorted = lastSorted->next; } else { ListNode *prev = dummyHead; while (prev->next->val <= curr->val) { prev = prev->next; } lastSorted->next = curr->next; curr->next = prev->next; prev->next = curr; } curr = lastSorted->next; } return dummyHead->next; } };
元素集合越接近有序,直接插入排序算法的时间效率越高
插入排序的过程不会破坏原有数组中相同关键字的相对次序,所以插入排序是一种稳定的排序算法。
最优情况下:时间效率为O(N)
最差情况下:时间复杂度为O(N2)
空间复杂度:O(1) 它是一种稳定的排序算法
希尔排序
希尔排序又称缩小增量排序,是对插入排序的优化:
举个例子,对数组 [84, 83, 88, 87, 61, 50, 70, 60, 80, 99] 进行希尔排序的过程如下:
5
间隔排序):按照间隔 5
分割子数组,共分成五组,分别是
[84, 50], [83, 70], [88, 60], [87, 80], [61, 99]
对它们进行插入排序,排序后它们分别变成:
[50, 84], [70, 83], [60, 88], [80, 87], [61, 99]
此时整个数组变成 [50, 70, 60, 80, 61, 84, 83, 88, 87, 99]
按照间隔 2 分割子数组,共分成两组,分别是
[50, 60, 61, 83, 87], [70, 80, 84, 88, 99]
对他们进行插入排序,排序后它们分别变成:[50, 60, 61, 83, 87], [70, 80, 84, 88, 99]
此时整个数组变成 [50, 70, 60, 80, 61, 84, 83, 88, 87, 99]
这里有一个非常重要的性质:当我们完成 2 间隔排序后,这个数组仍然是保持 5 间隔有序的。也就是说,更小间隔的排序没有把上一步的结果变坏。
按照间隔 1 分割子数组,分成一组,也就是整个数组。对其进行插入排序,经过前两遍排序,数组已经基本有序了,所以这一步只需经过少量交换即可完成排序。排序后数组变成 [50, 60, 61, 70, 80, 83, 84, 87, 88, 99],整个排序完成。
其中,每一遍排序的间隔在希尔排序中被称为增量,所有的增量组成的序列称为增量序列,也就是本例中的[5,2,1].增量依次递减,最后一个增量必须为1,所以希尔排序又被称之为[缩小增量排序]
以专业术语来描述希尔排序,可以分为以下两个步骤:
有一条非常重要的性质保证了希尔排序的效率:
「 D k + 1 D_{k+1} Dk+1间隔」 有序的序列,在经过 「 D k D_k Dk间隔」 排序后,仍然是 「 D k + 1 D_{k+1} Dk+1间隔」 有序的
void shellSort(vector<int>& arr) { // 间隔序列,在希尔排序中我们称之为增量序列 for (int gap = arr.size() / 2; gap > 0; gap /= 2) { // 分组 for (int groupStartIndex = 0; groupStartIndex < gap; groupStartIndex++) { // 插入排序 for (int currentIndex = groupStartIndex + gap; currentIndex < arr.size(); currentIndex += gap) { // currentNumber 站起来,开始找位置 int currentNumber = arr[currentIndex]; int preIndex = currentIndex - gap; while (preIndex >= groupStartIndex && currentNumber < arr[preIndex]) { // 向后挪位置 arr[preIndex + gap] = arr[preIndex]; preIndex -= gap; } // currentNumber 找到了自己的位置,坐下 arr[preIndex + gap] = currentNumber; } } } }
实际上,这段代码可以优化一下。我们现在的处理方式是:处理完一组间隔序列后,再回来处理下一组间隔序列,这非常符合人类思维。但对于计算机来说,它更喜欢从第 gap 个元素开始,按照顺序将每个元素依次向前插入自己所在的组这种方式。虽然这个过程看起来是在不同的间隔序列中不断跳跃,但站在计算机的角度,它是在访问一段连续数组。
void shellSort(vector<int>& arr) { // 间隔序列,在希尔排序中我们称之为增量序列 for (int gap = arr.size() / 2; gap > 0; gap /= 2) { // 从 gap 开始,按照顺序将每个元素依次向前插入自己所在的组 for (int i = gap; i < arr.size(); i++) { // currentNumber 站起来,开始找位置 int currentNumber = arr[i]; // 该组前一个数字的索引 int preIndex = i - gap; while (preIndex >= 0 && currentNumber < arr[preIndex]) { // 向后挪位置 arr[preIndex + gap] = arr[preIndex]; preIndex -= gap; } // currentNumber 找到了自己的位置,坐下 arr[preIndex + gap] = currentNumber; } } }
希尔排序与 O( n 2 n^2 n2) 级排序算法的本质区别:
相对于冒泡排序、选择排序、插入排序(时间复杂度为O( n 2 n^2 n2) )来说,希尔排序的排序过程显得较为复杂,接下来我们来分析一个有趣的问题:希尔排序凭什么可以打破时间复杂度 O( n 2 n^2 n2) 的魔咒呢?它和O( n 2 n^2 n2) 级排序算法的本质区别是什么?
这个问题我们可以用逆序对来理解:
当我们从小到大排序时,在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。
排序算法本质上就是一个消除逆序对的过程。
对于随机数组,逆序对的数量是 O( n 2 n^2 n2) 级的,如果采用「交换相邻元素」的办法来消除逆序对,每次最多只能消除一组逆序对,因此必须执行 O( n 2 n^2 n2) 级的交换次数,这就是为什么冒泡、插入、选择算法只能到 O( n 2 n^2 n2) 级的原因。反过来说,基于交换元素的排序算法要想突破 O( n 2 n^2 n2) 级,必须通过一些比较,交换间隔比较远的元素,使得一次交换能消除一个以上的逆序对。
希尔排序算法就是通过这种方式,打破了在空间复杂度为 O(1)的情况下,时间复杂度为 O( n 2 n^2 n2) 的魔咒,此后的快排、堆排算法也都是基于这样的思路实现的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。