直接排序(插入排序)
方法:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列
应用场景 :接近有序 数据量较少
接近有序:小的元素在前 大的在后 不大不小的在中间
空间复杂度:O( 1) 不需要额外的空间
时间复制度:O(n^2) 最坏情况下 为:1+2+3+4········+n
稳定性:稳定
(稳定性的定义: 假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。)
void Sort(int array[], int size)
{//应用场景 :接近有序 数据量较少
//接近有序:小的元素在前 大的在后 不大不小的在中间
//空间复杂度:O( 1)
//时间复制度:O(n)
/*for (int i = 1; i < size; i++)
{
int key = array[i];
int end = i - 1;
while (end >= 0 && key < array[end])
{
array[end+1] = array[end];
end--;
}
array[end+1] = key;
}*/
for (int i = 1; i < size; i++)
{
int j = i
while (j>0 &&array[j] < array[j - 1])
{
swap(&array[j], &array[j - 1]);
j--;
}
}
}
希尔排序
和直接排序类似,但在排序前先对数据分组,先对分组后的数据用直接排序,然后逐渐缩短分组间距用直接排序,直到正常情况下的直接排序。相对于直接排序,时间复杂度更低。
空间复杂度:O(1)
时间复杂度:O(n^1.2 ~n ^1.6) 目前仍没有好的增量序列
让gap == 元素个数 然后每次 gap = gap / 3 + 1,这种效率高。
void ShellSort(int array[], int size,int gap)
{
gap = size;
while (gap>1)
{
gap = gap / 3 + 1;//这种效率高
for (int i = gap; i < size; i++)
{//这种不需要反复交换两个元素
int end = i - gap;
int key = array[i];
while (end>=0 && key < array[end])
{
array[end + gap] = array[end];
end -= gap;
}
array[end+gap] = key;
}
//gap--;
}
}
选择排序
遍历 找到最大的 和最后交换 size-- 直到size==1;
void Select(int *array, int size)//只找最大的 和末尾交换
{
while (size>1)
{
int maxpos = 0;
for (int i = 1; i < size; i++)
{
if (array[i]>array[maxpos])
maxpos = i;
}
Swap(&array[maxpos], &array[size - 1]);
size--;
}
}
选择排序的改进
遍历 同时找最大的和最小的 最小的放左端 最大的放右端 用begin 和end来记录两端的位置
特殊情况:如果最小的刚好在最右端 的情况 ,在最大的和最右端交换时,最小的位置 就为最大的原先的位置了。
void Select_OP(int *array, int size)//同时记录最大和最小的 两头插入
{
int begin = 0;
int end = size - 1;
while (begin<end)
{
int maxpos = begin;
int minpos = begin;
for (int i = begin; i <= end; i++)
{
if (array[i]>array[maxpos])//找最大的位置
maxpos = i;
if (array[i] < array[minpos])//找最小的位置
minpos = i;
}
if (maxpos != end)
Swap(&array[maxpos], &array[end]);
if (minpos == end)//这一步非常容易出错 如果最后一个元素恰好也是最小的情况
minpos = maxpos;
if (minpos!=begin)
Swap(&array[minpos], &array[begin]);
begin++;
end--;
}
}
时间复杂度:因为每次都需要遍历找最大值 所以为O(n^2)
空间复杂度:O(1)
稳定性:不稳定
快速排序
以任意元素X为基准,将数组分为两部分 ,左边部分小于X,右边大于X,X放中间
排好后 左边部分都比X小 右边部分均大于等于X 因此X的位置不会再变化
再分别对X的左边和X的右边部分排序 (X不需要再参与任何一边)
直到每部分只有一个元素 即begin<end
Partion部分,当begin指向第一个大于X的元素,end指向第一个小于X的元素时 交换
注意下面这两行代码:
QuickSort(array, left, div-1);这里div的位置确定后不会再参与两边的排序
QuickSort(array, div+1, right);
if (begin != right - 1||array[begin]>key)
特殊情况: 基准值为最大值 则begin == rigth-1说明前面的元素均比基准点小 这时需要判断 begin和基准值的大小 若begin对应值大 交换begin和基准值即可 否则不交换
若begin!=right-1 说明前面发生了交换 直接交换begin和基准值即可
int Partion(int array[], int left, int right)
{
int begin = left;
int end = right-1;
int key = array[right];
while (begin<end)
{
while (begin<end && array[begin] < key)
begin++;
while (begin<end && array[end] >= key)
end--;
if (begin<end)
Swap(&array[begin], &array[end]);
}
if (begin != right - 1||array[begin]>key)
Swap(&array[begin], &array[right]);//把基准点放到中间位置
return begin;//返回基准点的位置
}
void QuickSort(int array[], int left, int right)
{//left 和 right 表示下标
if (right >left)
{
int div = Partion(array, left, right);
QuickSort(array, left, div-1);
QuickSort(array, div+1, right);
}
}
时间复杂度:
1.最好情况: 每次都能以基准点将队列均分 这种情况下类似于完全二叉树
复杂度为 树的深度logn 乘以 n 为nlogn
2.最坏的情况; 队列为逆序 每次只能从队列中分出一个元素 类似于一颗斜树
复杂度为n^2
稳定性:不稳定
快速排序优化
1.优化基准点(枢轴)
上面分析了最好情况和和最坏情况是看基准点能否尽量平分队列,因此基准点应该尽量取中间的值
改进方法:三数取中:一般取左端 右端 中间三个数 选中间大的数作为基准点
2.优化不必要的交换
挖坑法用替换来取代排序中的交换
3.优化小组是的排序方案
快速排序中用到了递归,但由于其算法的复杂度较低,在处理大量数据时 有优势
但处理少量数据时 其效率不如直接排序 因此可以设定一个值 以7为例
void QuickSort(int array[], int low, int high)
{//low 和 high 表示序列中的最小 最大下标值
if (high - low>7)
{
int div = Partion1(array, low, high);
QuickSort(array, low, div);
QuickSort(array, div + 1, high);
}
else
DirectSort(array,high-low);
}
4.尾递归
冒泡排序
从第一个元素(或最后一个元素)开始,若比右(左)边元素大 (小) 交换。
void BubbleSort(int array[], int size)
{
while (size>1)
{
for (int i = 0; i < size-1; i++)
{
if (array[i] > array[i + 1])
{
Swap(&array[i], &array[i + 1]);
}
}
size--;
}
}
优化:如果没有发生元素的交换说明已经完全有序
void Bubble(int array[], int size)
{
int j = size;
while (j)
{
int flag = 0;
for (int i = j - 1; i > 0; i--)//从最后一个开始 把小的放前面
{
if (array[i - 1] > array[i])
{
Swap(&array[i - 1], &array[i]);
flag = 1;
}
}
if (!flag)//如果没有发生元素的交换 说明已经全部有序
break;
j--;
}
}
时间复杂度:
考虑最坏情况 n+(n-1)+(n-2)…1 = O(n^2)
空间复制度:O(1)
稳定性:稳定
挖坑法排序
左右指针 begin 和 end分别指向头尾 ,选基准值(假设以最后一个元素X为基准值)
第一步.begin所指向的元素 如果比X小 begin++
若比X大 则把begin指向元素移动到end begin现在指向元素形象的叫做‘坑’ end- -
第二步.若end指向的元素比X大 end- -
若比X小 填入 begin指向的坑中 end指向的元素成为‘坑’ 返回第一步
当end和begin指向同一位置时 把X放在这个位置
特殊情况:
基准值是最大值 则begin == right-1 这时需要判断 若array[begin]>key 则交换 否则不交换
int Partion1(int array[], int left, int right)
{
int begin = left;
int end = right;
int key = array[right];
while (begin<end)
{
while (begin < end&&array[begin]<key)
begin++;
if (begin<end)
array[end] = array[begin];
while (end>begin&&array[end] >= key)
end--;
if (begin<end)
array[begin] = array[end];
}
if (begin != right - 1 || array[begin]>key)
array[begin] = key;
return begin;
}
两个指针快速排序
cur和pre一前一后 以最后一个元素X为基准值 先让cur = 0指向第一元素 pre= cur-1;
如果cur小于X 让pre后移
1.若pre==cur 让cur后移
2.若pre!=cur 交换pre和cur指向元素 cur后移
如果cur大于 让cur后移 pre不动
直到cur指向最后一个元素
最后让最后一个元素x和pre++指向的元素交换
其实就是让cur去找小于X的元素 让pre留在第一个大于X的元素前 当cur找到后 让pre++ 然后交换
int Partion3(int array[], int left, int right)
{
int key = array[right];
int cur = left;
int pre = cur - 1;
while(cur < right)
{
if (array[cur]<key&&++pre != cur)
{
Swap(&array[cur], &array[pre]);
}
cur++;
}
if (++pre!=right)
Swap(&array[right], &array[++pre]);
return pre;
}
快排适用于元素没有规律 最理想的情况下:每次都能以基准点平分元素
当元素接近有序时 时间复杂度越高
归并排序
void merge(vector<int>&obj, int left, int right)
{
vector<int>help(right - left + 1);
int lo = left;
int hi = (left + right) / 2+1;
int i = 0;
while (lo <= (right - left) / 2 && hi <= right)
help[i++] = obj[lo] > obj[hi] ? obj[lo++] : obj[hi++];
while (lo <= (right - left) / 2)
help[i++] = obj[lo++];
while (hi <= right)
help[i++] = obj[hi++];
obj = help;
}
void Mergesort(vector<int>&obj, int left, int right){
if (left < right){
int mid = (left + right) / 2;
Mergesort(obj, left, mid);
Mergesort(obj, mid + 1, right);
merge(obj, left, right);
}
}
void Mergesort(vector<int>&obj){
if (obj.size() < 2)
return;
merge(obj, 0, obj.size() - 1);
}