目录
几种排序的介绍
冒泡排序
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
下面以对 3 2 4 1 进行冒泡排序说明。
第一轮 排序过程
3 2 4 1 (最初)
2 3 4 2 (比较3和2,交换)
2 3 4 1 (比较3和4,不交换)
2 3 1 4 (比较4和1,交换)
第一轮结束,最大的数4已经在最后面,因此第二轮排序只需要对前面三个数进行再比较。
第二轮 排序过程
2 3 1 4 (第一轮排序结果)
2 3 1 4 (比较2和3,不交换)
2 1 3 4 (比较3和1,交换
第二轮结束,第二大的数已经排在倒数第二个位置,所以第三轮只需要比较前两个元素。
第三轮 排序过程
2 1 3 4 (第二轮排序结果)
1 2 3 4 (比较2和1,交换)
至此,排序结束。
选择排序
选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
第1轮 排序过程 (寻找第1小的数所在的位置)
3 2 4 1(最初, min_index=1)
3 2 4 1(3 > 2, 所以min_index=2)
3 2 4 1(2 < 4, 所以 min_index=2)
3 2 4 1(2 > 1, 所以 min_index=4, 这时候确定了第1小的数在位置4)
1 2 4 3 (第1轮结果,将3和1交换,也就是位置1和位置4交换)
第2轮 排序过程 (寻找第2小的数所在的位置)
1 2 4 3(第1轮结果, min_index=2,只需要从位置2开始寻找)
1 2 4 3(4 > 2, 所以min_index=2)
1 2 4 3(3 > 2, 所以 min_index=2)
1 2 4 3(第2轮结果,因为min_index位置刚好在第2个位置,无需交换)
第3轮 排序过程 (寻找第3小的数所在的位置)
1 2 4 3(第2轮结果, min_index=3,只需要从位置2开始寻找)
1 2 4 3(4 > 3, 所以min_index=4)
1 2 3 4(第3轮结果,将3和4交换,也就是位置4和位置3交换)
插入排序
插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:
(1)从第一个元素开始,该元素可以认为已经被排序;
(2)取出下一个元素,在已经排序的元素序列中从后向前扫描;
(3)如果该元素(已排序)大于新元素,将该元素移到下一位置;
重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
将新元素插入到该位置后;
在实际使用中,通常是排序整个无序数组,所以把这个无序数组分为两部分排序好的子数组和待插入的元素。第一轮时,将第一个元素作为排序好的子数组,插入第二个元素;第二轮,将前两个元素作为排序好的数组,插入第三个元素。以此类推,第i轮排序时,在前i个元素的子数组中插入第i+1个元素。直到所有元素都加入排序好数组。
第1轮
[ 3 ] [ 2 4 1 ] (最初状态,将第1个元素分为排序好的子数组,其余为待插入元素)
[ 3 ] [ 2 4 1 ] (由于3>2,所以待插入位置j=1)
[ 2 3 ] [ 4 1 ] (将2插入到位置j)
第2轮
[ 2 3 ] [ 4 1 ] (第1轮排序结果)
[ 2 3 ] [ 4 1 ] (由于2<4,所以先假定j=2)
[ 2 3 ] [ 4 1 ] (由于3<4,所以j=3)
[ 2 3 4 ] [ 1 ] (由于4刚好在位置3,无需插入)
第3轮
[ 2 3 4 ] [ 1 ] (第2轮排序结果)
[ 2 3 4 ] [ 1 ] (由于1<2,所以j=1)
[1 2 3 4 ] (将1插入位置j,待排序元素为空,排序结束)
快速排序
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。6.1 算法描述
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
从数列中挑出一个元素,称为 “基准”(pivot);
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
LeetCode作业
看到O(n log n)这个复杂度就想到了快速排序法。快速排序法主要思想为取一个基准点,采用交换+分治法完成快速排序。
对于单链表的快速排序,不能从后面开始遍历,因为根据后继找不到其前驱。因此采用以下步骤:
1.取每次的头节点为基准节点,然后将这个节点从链表分离出来。用两个指针,从头节点的下一个节点遍历。
2.找到一个小于头节点的值,若是第一个小于头节点的值,左链表的头指向它,然后继续遍历,满足小于就挂载在左链表的后面。相反,创建一个有链表。其实还是在原链表上创建。
3.使用递归分别对左链表和右链表进行如上操作,当头节点为NULL时,递归返回。当左右链表都返回时,将原来的头节点与左右链表挂上。
class Solution {
public:
ListNode* sortList(ListNode* head)
{
ListNode* endpoint = head;
while (endpoint->next)
endpoint = endpoint->next;
QuickSort(head, endpoint);
return head;
}
private:
void QuickSort(ListNode* pBeign, ListNode* pEnd)
{
if (pBeign != pEnd)
{
ListNode* partion = GetPartion(pBeign, pEnd);
QuickSort(pBeign, partion);
if (partion->next)
QuickSort(partion->next, pEnd);
}
}
ListNode* GetPartion(ListNode* pBegin, ListNode* pEnd)
{
int key = pBegin->val;
ListNode* p = pBegin;
ListNode* q = p->next;
while (q != pEnd->next)
{
if (q->val < key)
{
p = p->next;
swap(p->val, q->val);
}
q = q->next;
}
swap(p->val, pBegin->val);
return p;
}
};