文章目录
一、排序链表
1、题目描述——LeetCode.148
在 O(n logn) 时间复杂度和常数级空间复杂度下,对链表进行排序。
示例 1:
输入: 4->2->1->3
输出: 1->2->3->4
示例 2:
输入: -1->5->3->4->0
输出: -1->0->3->4->5
2、分析
本实现采用快速排序算法实现
(1)一般的快排
需要一个指针指向头,一个指针指向尾,然后两个指针相向运动并按一定规律交换值,最后找到一个支点使得支点左边小于支点,支点右边大于支点
==》如果是这样的话,对于单链表我们没有前驱指针,怎么能使得后面的那个指针往前移动呢?
==》使两个指针都往next方向移动并且能找到支点那就好了。
(2)解题思路
只需要两个指针p和q,这两个指针均往next方向移动,移动的过程中保持p之前的key都小于选定的key,p和q之间的key都大于选定的key,那么当q走到末尾的时候便完成了一次支点的寻找。
3、实现
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* sortList(ListNode* head) {
if(head == NULL)
return NULL;
ListNode *right = head;
while(right->next)
right = right->next;
QuickSort(head, right);
return head;
}
private:
void QuickSort(ListNode *left, ListNode *right)
{
if(left != right)
{
ListNode *partion = GetPartion(left, right);
QuickSort(left, partion);
if(partion->next)
QuickSort(partion->next, right);
}
}
ListNode *GetPartion(ListNode *left, ListNode *right)
{
int key = left->val;
ListNode *p = left;
ListNode *q = p->next;
while(q != right->next)
{
if(q->val < key)
{
p = p->next;
swap(p->val, q->val);
}
q = q->next;
}
swap(p->val, left->val);
return p;
}
};
二、排序算法
排序方法 | 时间复杂度 | 是否基于比较 | 适用性 | |
---|---|---|---|---|
1 | 冒泡、插入、选择 | O(n2) | √ | 适合小规模数据排序 |
2 | 快排、归并 | O(nlogn) | √ | 适合大规模的数据排序 |
3 | 桶、计数、基数 | O(n) | × |
三、插入排序
1、基本思想
将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表。
(1)过程概述
插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
(2)具体算法描述:
① 从第一个元素开始,该元素可以认为已经被排序;
② 取出下一个元素,在已经排序的元素序列中从后向前扫描;
③ 如果该元素(已排序)大于新元素,将该元素移到下一位置;
④ 重复步骤 ③,直到找到已排序的元素小于或者等于新元素的位置;
⑤ 将新元素插入到该位置后
⑥ 重复步骤②~⑤
2、示例
原数据:{4,5,6,1,3,2}
排序过程:其中左侧是已排序区间,右侧是未排序区间。
3、实现
void insertSort(int a[], int len){
int tmp, j;
for(int i = 1; i < len; i++){
tmp = a[i];
int j = i - 1;
while(tmp < a[j])
{
a[j + 1] = a[j];
j--;
}
a[j + 1] = tmp;
}
}
4、分析
(1)原地排序算法
- 空间复杂度为 O(1)
==》原地排序算法
(2)稳定排序算法
- 对于值相同的元素,可以选择将后面出现的元素,插入到前面出现元素的后面。
==》保持原有前后顺序,也就是稳定的排序算法
(3)时间复杂度
- 最好情况时间复杂度(数据已经有序):O(n)
- 最坏情况时间复杂度:O(n2)
- 平均时间复杂度:O(n2)
- 对于插入排序来说,每次插入操作(时间复杂度为O(n))都相当于在数组中插入一个数据,循环执行 n 次插入操作
四、快速排序
1、基本思想
基于分治思想,快速排序算法:选择一个基准数,通过一趟排序将要排序的数据分割成独立的两部分;其中一部分的所有数据都比另外一部分的所有数据都要小。然后,再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
(1)快速排序的流程
① 从数列中挑出一个基准值(枢轴,pivot)。
② 将数组进行划分(partition),将比基准数大的元素都移至枢轴右边,将小于等于基准数的元素都移至枢轴左边。在这个分区退出之后,该基准就处于数列的中间位置。
③ 递归地把"基准值前面的子数列"和"基准值后面的子数列"进行排序。
2、实现
- 实现一
将第一个元素设置为基准值来划分区间,基准值的位置始终不变,最后交换使得基准值的位置。
// left、right分别表示要交换数组的第一个元素和最后一个元素的位置
int partition(int arr[], int left, int right)
{
int i = left + 1;
int j = right;
// 将第一个元素设置为 基准值
int temp = arr[left];
while(i <= j)
{
while(arr[i] < temp)
i++;
while(arr[j] > temp)
j--;
if(i < j)
swap(arr[i++], arr[j--]);
else
i++;
}
swap(arr[j], arr[left]);
return j;
}
void quick_sort(int arr[], int left, int right)
{
if(left > right)
return;
int j = partition(arr, left, right);
quick_sort(arr, left, j - 1);
quick_sort(arr, j + 1, right);
}
- 实现二
将第一个元素设置为基准值来划分区间,将基准值不断与冲突值进行换位。
void QuickSort(int arr[], int left, int right)
{
int i = left;
int j = right;
int temp = arr[i];
if(i < j)
{
while(i < j)
{
while(i < j && arr[j] >= temp)
j--;
if(i < j)
{
arr[i] = arr[j];
i++;
}
while(i < j && arr[i] < temp)
i++;
if(i < j)
{
arr[j] = arr[i];
j--;
}
}
// 将基准数放在 i 位置
arr[i] = temp;
// 递归实现
QuickSort(arr, left, i - 1);
QuickSort(arr, i + 1, right);
}
}
3、性能分析
- 不稳定
- 原地排序——空间复杂度为O(1)
- 时间复杂度:大部分情况下的时间复杂度都可以做到 O(nlogn),只有在极端情况下,才会退化到 O(n2)。