1. 常见的排序方法和时间复杂度。
首先明确下,什么是稳定排序,队列中存在两个相等的数字排序过程中,这两个数字的先后顺序如果不会发生变化,就叫做稳定排序,否则不稳定。
几种排序的情况
排序方法 平均情况 最好情况 最坏情况 辅助空间 稳定性
冒泡排序 O(n^2) O(n) O(n^2) O(1) 稳定
简单选择 O(n^2) O(n^2) O(n^2) O(1) 稳定
直接插入 O(n^2) O(n) O(n^2) O(1) 稳定
希尔排序 O(nlogn)~O(n^2) O(n^1.3) O(n^2) O(1) 不稳定
堆排序 O(nlogn) O(nlogn) O(nlogn) O(1) 不稳定
归并排序 O(nlogn) O(nlogn) O(nlogn) O(n) 稳定
快速排序 O(nlogn) O(nlogn) O(n^2) O(logn)~O(n)不稳定
外排序:需要在内外存之间多次交换数据才能进行
内排序:
插入类:直接插入; 希尔排序
选择类排序:简单选择,堆排序
交换类排序:冒泡排序,快速排序
归并类排序: 归并排序
插入排序是一种简单的排序方法,基本思想是通过构建有序序列,对于未排序的数据,在已排序序列中从后往前扫描,找到相应位置并插入。思考抓牌的过程。
希尔排序:对直接插入排序的一种优化,就是把直接插入排序改成了分组插入排序。基本思想是将整个待排序元素序列按照gap分割成为N个组,对每个组进行直接插入排序,然后再减小gap进行直接插入排序,直到gap达到最小时,即数组基本达到有序时,再对数组进行直接插入排序,此时直接插入排序就可以达到最高效率。
选择排序:每次从无序数组中找到最小的放到最前面,再从剩下的元素中找出次小的放在无序区的最前面,直到数组有序为止。
堆排序:使用大顶堆升序,基本实现原理也是一种选择排序,它同样是确定了为止选择符合位置的元素,但是堆排序是更加优化的选择排序的版本,它利用了堆的特性,父节点的值大于子节点,且满足完全二叉树,大大提高了选择排序的效率。
冒泡排序:交换式的排序方式,基本思想是相近的两个数字比较,小的放在前面,大的放在后面,按照这个规则从头向后比较,最大的数就沉在了数组的最后。
快速排序:应用场景是大规模的数据排序,并且实际性能要好于归并排序。它的基本原理是从数组中选取一个元素,把所有大于它的元素都放在它的后面,所有小于它的元素都放在它的前面,然后这个元素就把原数组分成了两个部分,再分别进行同样的操作,直到数组不能再切分此时数组有序。
归并排序:归并是指将两个或者若干个有序表组合成一个新的有序表,归并排序和快速排序一样也是采用分治的思想,它的基本原理是通过对若干个有序节点序列的合并为一个有序序列来实现排序的。
2.快速排序的代码:
class Solution {
public:
void sortColors(vector<int>& nums)
{
int n=nums.size();
if(n<=1) return;
sort2(nums,0,n-1);
}
void sort2(vector<int>& nums,int start, int end)
{
if(start<end)
{
int p=partition(nums,start,end);
sort2(nums,start,p-1);
sort2(nums,p+1,end);
}
}
int partition(vector<int> &nums,int start,int end)
{
int x=nums[end];
int i=start-1; //i记住最后一个小于x的元素的下标
int j;
for(j=start;j<end;j++)
{
if(nums[j]<=x)
{
i++;
swap(nums[i],nums[j]);
}
}
nums[end]=nums[i+1];
nums[i+1]=x;
return i+1; //返回中间元素的位置
}
}
3.堆排序
堆排序的基本思想:将待排序的序列构造成为一个大顶堆,此时整个序列的最大值就是堆顶元素,然后将其和末尾元素进行交换,末尾元素就是最大值了,然后将剩下的n-1个元素重新构造成一个堆,这样会得到n个元素的次小值,如此反复执行就能得到一个有序序列。
主要是建立堆的过程比较麻烦,建立堆的过程可以分为对每个非叶子节点的调整过程,先找到第一个非叶子节点,进行堆调整,依次向二叉树上面走,不断进行堆调整。直到堆顶元素,建堆结束得到的堆顶元素就是数组的最大元素。
void adjust(int a[], int start, int end)
{
int temp=a[start]; //记住当前节点
int i=2*start+1; //当前节点的第一个左子树节点
while(i<=end)
{
//找出start的左右子节点中最大的那个
while (i+1<=end && a[i+1]>a[i]) i++;
//满足堆的定义,不用调整,break
if(a[i]<=temp) break;
//最大子节点向上调整, 替换掉其父节点
a[start]=a[i];
start=i;
i=2*start+1;
}
a[start]=temp;
}
//堆排序过程
void heapsort(int a[], int len)
{
//第一次建立最大堆的过程
for(int i=len/2-1; i>=0; i--)
adjust(a, i, len-1);
//进行堆排序, 调整len-1次,交换第一个元素和当前数组最后一个元素
for (int i=len-1; i>0; i--)
{
swap(a[i], a[0]);
adjust(a, 0, i-1);
}
}
4. 归并排序代码
class Solution
{
public:
void sortColors(vector<int>& nums)
{
int n=nums.size();
if(n<=1) return;
process(nums,0,n-1);
}
void process(vector<int> &data,int start,int end)
{
if(start>=end) return;
vector<int> copy(data);
int mid=(start+end)/2;
process(data,start,mid); //左边有序
process(data,mid+1,end); //右边有序
int p=mid; //指向左边的最后一个位置
int q=end; //指向右边的最后一个位置
int k=end; //拷贝数组的最后一个位置
while(p>=start && q>=mid+1) //归并排序的主要逻辑,将两边有序的数组合并为一个数组
{
if(data[p]>data[q])
{
copy[k--]=data[p--];
}
else
{
copy[k--]=data[q--];
}
}
while(p>=start) copy[k--]=data[p--];
while(q>=mid+1) copy[k--]=data[q--];
for(int i=start;i<=end;i++) //copy是有序的,将它拷贝给data,这样data也是有序的。
{
data[i]=copy[i];
}
}
};