一、插入排序
1.直接插入排序
最开始选取一个有序区间,例如红色部分,这里认为第一个数就是有序区间,然后让第二个数往有序区间里插入,按升序排列,5比2大,放在其后,重新构成一个有序区间,依次再把后面的4拿出来,可以先放在定义一个tmp里,再与前面的有序区间比较,这里定义一个end,4比5小,5就放在end+1处,end-- 到2处,4再与之比较,大于放在其后,以此类推,直至排好。end--的结束条件
void InsertSort(int* a, size_t n)
{
assert(a);
for (size_t i = 0; i < n-1; i++)
{
int end = i;
int tmp = a[end + 1];
for (; end >= 0;end--)
{
if (a[end] > tmp)
a[end + 1] = a[end];
else
break;
}
a[end + 1] = tmp;
}
}
时间复杂度:O(N^2)
直接插入排序的劣势:如果数组是近似逆序的数组它的效率就比较低了。
对于接近有序的就比较好,挪动数据挪的少,效率就高了。
2.希尔排序(是 直接插入排序 的优化)
(1)预排序 先定义一个 gap,比如 gap = 3,gap是几就会被先分成几组,每组的数据 n/gap
先排红线所指的数据, 6 拿出来给 tmp ,同 end=9 作比较,6 小于 9 ,9 往后挪,不是挪一个位置而是 gap 个位置,然后 end减 gap,即此时end在数组的前面-3处;end又放在 9 现在的位置,就是图中 end’的位置;然后 3再跟 9 、6 比较,1 再和 9、6、3比较;同样的,5 和 8 比较,交换后,2 和8、5 比较交换;最后换绿线排序。为了接近有序,小的数基本都在前面了。
(2)插入排序
结合起来的代码是
void ShellSort(int* a, size_t n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (size_t i = 0; i < n - gap; i++) //代码中走的流程是 9 和 5 比较交换,接下来是 8 和 4
{ //比较交换,依次进行,所以循环 n-gap 次,
int end = i;
int tmp = a[end + gap];
for (; end >= 0; end -= gap)
{
if (a[end] > tmp)
{
a[end + gap] = a[end];
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
代码中走的流程是 9 和 5 比较交换,接下来是 8 和 4
{ //比较交换,依次进行,所以循环 n-gap 次,
int end = i;
int tmp = a[end + gap];
for (; end >= 0; end -= gap)
{
if (a[end] > tmp)
{
a[end + gap] = a[end];
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
时间复杂度:O(N)<希尔<O(N^2) 具体在N^1.25--1.6N^1.25之间
劣势:数据已经有序或接近有序、逆序,希尔排序会起到反作用;如果不是接近有序且数据量比较大,它就会比插入排序更优一些。
二、选择排序
(1)选择排序
遍历一遍,找出最小的数的下标,把它与第一个数交换,然后除过第一个数,再遍历一遍,找出最小的数,与本次遍历的第一个数交换,依次这样。
选择排序的优化思想:
第一次选出两个数放在两边,然后缩小区间,再选出两个数放两边,再缩小区间,再选,直到两个下标相遇或者错过的时候停止。
void SelectSort(int* a, size_t n)
{
assert(a);
size_t begin = 0;
size_t end = n - 1;
while (begin < end)
{
size_t max = begin, min = begin;
for (size_t i = begin; i <= end; i++)
{
if (a[i] > a[max])
{
max = i;
}
if (a[i] < a[min])
{
min = i;
}
}
swap(a[begin], a[min]);
//如果max占据了begin位置,则更正max的位置
if (begin == max) //针对特殊情况,比如 9、5、4、2、3、6、8、7、1、0最大和最小的分别占据两边,那么
{ //执行这两个交换语句 之后,两个数据又被换回了原来的位置,所以需要中间的if语句来
max = min; //更正。
}
swap(a[end], a[max]);
++begin;
--end;
}
}
针对特殊情况,比如 9、5、4、2、3、6、8、7、1、0最大和最小的分别占据两边,那么
{ //执行这两个交换语句 之后,两个数据又被换回了原来的位置,所以需要中间的if语句来
max = min; //更正。
}
swap(a[end], a[max]);
++begin;
--end;
}
}
时间复杂度:O(N^2)
选择排序是最坏的排序算法,因为就算是有序,也是N^2的效率,但是插入排序对于接近有序的数据可以不用交换。
直接选择排序是最直观的
(2) 堆排序
静态二叉树的思想,最适合完全二叉树。排一个升序,建大堆,写一个向下调整算法。
不要把 搜索树 和 大、小堆搞混了:搜索树是左边小,右边大;大堆是根节点大于孩子,左右孩子的大小无所谓,小堆相反。
void AjustDowd(int* a, size_t n, int root)
{
assert(a);
int parent = root;
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n && a[child] < a[child + 1])
{
++child;
}
if (a[child] > a[parent])
{
swap(a[child], a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
break;
}
}
void HeapSort(int* a, size_t n)
{
assert(a);
//建堆
for (int i = (n-2)/2; i >= 0; --i)
{
AjustDowd(a, n, i);//向下调算法
}
//排序
int end = n - 1;
while (end > 0)
{
swap(a[0], a[end]);
AjustDowd(a, end, 0);
--end;
}
}
时间复杂度:O(N*logN)
向下调整算法是 logN,在循环里是 N*logN,有两个则是 2N*logN,简化到O(N*logN)
三、交换排序
(1)冒泡排序
void BubbleSort(int* a, size_t n)
{
assert(a);
for (size_t end = n; end > 0; --end)
//for (size_t end = 0; end < n; ++end) //一开始的错误写法
{
int exchange = 0;
int first = 0, second = 1;
while (second < end)
{
if (a[first] > a[second])
{
swap(a[first], a[second]);
exchange = 1;
}
++first;
++second;
}
if (exchange = 0)
break;
}
}
时间复杂度:O(N)-O(N^2)
(2) 快速排序
先选出一个 Key 然后放到它对的位置,还有两个指针,一个在头begin一个尾end(左右指针法),让begin先走找大的值,找到大的与end的交换,然后再重复找,同时end也在找比Key小的值,找到后begin、end再交换,再走,到他们相遇就停止,而且停止的位置一定是比Key大的位置,最后将相遇的位置与Key一交换,此时就是左边都是比Key小的,右边都是比它大的,即Key就放在了正确的位置上
划分到足够小,直到有序,其实快排是一个递归
I.左右指针法
int PartSort(int* a, int begin, int end)
{
int key = end;
while (begin < end)
{
while (begin < end && a[begin] <= a[key])//begin找到比key大的后停止,执行下面的while循环,等到end
++begin;
while (begin < end && a[end] >= a[key])//找到小的后,交换;注意要用 >= 和<= 否则数组遇到相同的数据
--end; //会造成死循环
if (begin < end)
swap(a[begin], a[end]);
}
swap(a[begin], a[key]);
return begin;
}
//闭区间[left,right]
void QuickSort(int* a, int left, int right)
{
assert(a);
if (left < right)//保证这个区间至少有两个及以上的数
{
int div = PartSort(a,left,right);
QuickSort(a, left, div - 1);
QuickSort(a, div+1, right);
}
}
begin找到比key大的后停止,执行下面的while循环,等到end
++begin;
while (begin < end && a[end] >= a[key])//找到小的后,交换;注意要用 >= 和<= 否则数组遇到相同的数据
--end; //会造成死循环
if (begin < end)
swap(a[begin], a[end]);
}
swap(a[begin], a[key]);
return begin;
}
//闭区间[left,right]
void QuickSort(int* a, int left, int right)
{
assert(a);
if (left < right)//保证这个区间至少有两个及以上的数
{
int div = PartSort(a,left,right);
QuickSort(a, left, div - 1);
QuickSort(a, div+1, right);
}
}
II.挖坑法
begin ,end 同样指向头和尾,选了end位置作了 key,先把key保存到另一个变量里,如上图,tmp=5;所以5这个位置就空了,当begin 找到大 的时候,就放在空的这个位置,end再找小,放在刚大 的那个空的位置上,然后begin找,end找,不断循环,begin,end相遇就停下来,把key放入这个坑。
int PartSort2(int* a, int begin, int end)
{
int tmp = a[end];
while (begin < end)
{
while (begin < end && a[begin] <= tmp)
++begin;
if (begin < end)
a[end] = a[begin];
while (begin < end && a[end] >= tmp)
--end;
if (begin < end)
a[begin] = a[end];
}
a[begin] = tmp;
return begin;
}
III.前后指针法
cur不断往后走,遇到比key小的 ++,同时prev也 ++;当cur遇到比key大的,prev 不++,cur继续往后走,当cur又遇到比key小的,prev++,然后cur与prev的值交换,cur继续往后走直到走到 key处停止,cur等于key,prev也++,最后该交换交换,key也就放到了正确的位置。
int PartSort3(int* a, int begin, int end)
{
int prev = begin - 1;
while (begin < end)
{
while (a[begin] < a[end] && ++prev != begin)
{
swap(a[prev], a[begin]);
}
++begin;
}
swap(a[++prev], a[end]);
return prev;
}
对于这三种方法,效率其实差不多,前两种都是大的往后翻,小的往前翻,翻来翻去的,而第三种方法就比较新颖,一前一后走。如果对单链表进行排序,左右指针就不可以用了,而前后指针就可以,对链表排序用冒泡比较好,但是现在可以用快排的第三种方法了。
再优化:
I.三数取中法
int GetMidIndex(int* a, int left, int right)
{
int mid = left + ((right - left) >> 1);//取中,不要用两数相加除以二
if (a[left] > a[mid])
{
if (a[mid] > a[right])
{
return mid;
}
else if (a[left] > a[right])//a[left]>a[mid] a[mid]<a[right]
{
return right;
}
else
return left;
}
else//a[left]<a[mid]
{
if (a[left] > a[right])
{
return left;
}
else if (a[mid] > a[right])//a[left]<a[right]
{
return right;
}
else
return mid;
}
}
求得的中点,随便带到前面的哪个方法中都可以优化
II.小区间优化法
void QuickSort2(int* a, int left, int right)
{
assert(a);
if (left >= right)
return;
else if (right - left < 5)
{
InsertSort(a + left, right - left + 1);
}
else//保证这个区间至少有两个及以上的数
{
int div = PartSort1(a, left, right);
QuickSort2(a, left, div - 1);
QuickSort2(a, div + 1, right);
}
}
非递归的写法
void QuickSortNonR(int* a, int begin, int end)
{
stack<int> s;
if (begin < end)
{
s.push(end);
s.push(begin);
}
while (!s.empty())
{
int left = s.top();
s.pop();
int right = s.top();
s.pop();
int div = PartSort1(a, left, right);
if (left < div - 1)
{
s.push(div - 1);
s.push(left);
}
if (div + 1 < right)
{
s.push(right);
s.push(div + 1);
}
}
}
四、归并排序
红色部分是拷贝一个同样大小的空间,将归并排好序的分区放入此空间
void _Merge(int* a, int* tmp, int begin1, int end1, int begin2, int end2)
{
int index = begin1;
int start = begin1, finish = end2;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
tmp[index++] = a[begin1++];
else
tmp[index++] = a[begin2++];
}
while (begin1 <= end1)
tmp[index++] = a[begin1++];
while (begin2 <= end2)
tmp[index++] = a[begin2++];
memcpy(a + start, tmp + start, (finish - start + 1)*sizeof(int));
}
void _MergeSort(int* a, int* tmp, int left, int right)
{
if (left >= right)
return;
int mid = left + ((right - left) >> 1);
_MergeSort(a, tmp, left, mid);
_MergeSort(a, tmp, mid + 1, right);
_Merge(a, tmp, left, mid, mid + 1, right);
}
void MergeSort(int* a, int n)
{
assert(a);
int* tmp = new int[n];
_MergeSort(a, tmp, 0, n-1);
delete[] tmp;
}