排序:将一组杂乱无章的数据按照一定的规则有组织地排列起来。
排序的稳定性:如果在排序中,存在前后相同的两个元素的话,排序前和排序后他们的相对位置不发生变化。
今天,先来学习插入排序和选择排序:
插入排序
直接插入排序
(1)思想:
每一步将一个待排序的元其排序码的大小,插入到前面已将排好序的一组元素的合适位置上去,直到全部元素插入完为止。
元素集合越接近有序,直接插入排序算法的时间效率越高。
(2)复杂度
最优:时间效率O(n) 已经有序,只需要比较n-1
最差:时间复杂度O(n^2)
数组全部为逆序,一共需要比较(n-1)+(n-2)+…+1,是一个等差数列
空间复杂度:O(1),它是一种稳定的排序算法。
(3)优缺点:
优点:元素很少的时候,效率很高
缺点:元素很多的时候,需要挪动很多数据,逆序或者接近逆序的情况下效率很差。
(4)图解:
(5)代码实现:
void Print(int *a,size_t n)
{
assert(a);
for(size_t i = 0;i<n;i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
void InsertSort(int* a,size_t n)
{
assert(a);
for(size_t i =1;i<n;i++)
{
int end = i-1;
int tmp = a[i];
while(end>=0)
{
if(tmp>=a[end])
break;
a[end+1] = a[end];
end--;
}
a[end+1] = tmp;
}
}
int main()
{
int a[8]= {6,1,0,5,2,9,7,3};
int len = sizeof(a)/sizeof(a[0]);
InsertSort(a,len);
Print(a,len);
}
希尔排序
是对直接插入排序的一种优化,在直接插入排序之前进行预排序,将较小的元素快速地放到前面。
(1)思想:
先把元素进行分组,每一组的每一个元素之间的间隙相等 ,对这些组分别进行直接插入排序. 将间隙缩小,重复上步骤,直到间隙值为1时,进行最后一趟直接插入排序。
(2)复杂度:
在逆序或者接近逆序的情况下比插入排序的效率高。
O(n)< 希尔排序的时间复杂度 < O(n*n)
空间复杂度:O(1) 占用有限的数组空间
(3)优缺点
优点:解决了直接插入排序在元素较多,接近逆序情况下需要挪动很多数据的缺陷。
缺点:在元素很多的时候,快速排序效率更高。
(4)图解:
(5)代码实现:
void ShellSort(int* a,size_t n)
{
assert(a);
size_t gap = n;
while(gap > 1)//while循环控制着多次循环
{
//该公式可以保证最后一趟排序的gap为1
gap = gap/3 + 1;
int end=n;
for(size_t i = 0;i<end-gap;i++)
{
int end = i;
int tmp = a[end+gap];//每组差值为gap
while(end >= 0 && a[end]>tmp)
{
if(tmp>=a[end])
break;
a[end+gap] = a[end];
end -= gap;
}
a[end+gap] = tmp;//进行插入
}
}
}
int main()
{
int a[9]= {9,8,7,6,5,4,3,2,1};
int len = sizeof(a)/sizeof(a[0]);
ShellSort(a,len);
Print(a,len);
}
选择排序
选择排序
(1)思想:
选择出当前范围内的最小值;将该最小值放入当前范围的第一个位置;缩小排序的范围,直到只有范围中只有一个元素为止。
(2)复杂度
时间复杂度:O(n*n) 每趟排序比较的次数是一个等差数列,是一种不稳定的排序算法
空间复杂度: O(1)
(3)图解:
(4)代码实现
void Print(int *a,size_t n)
{
assert(a);
for(size_t i = 0;i<n;i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
void SelectSort(int* arr,size_t len)
{
assert(arr>0);
assert(len>0);
for(int i = 0;i < len -1;i++)
{
for(int j = i+1;j<len;j++)
{
if(arr[j]<arr[i])
{
swap(arr[i],arr[j]);
}
}
}
}
int main()
{
int a[8]= {6,1,0,5,2,9,7,3};
int len = sizeof(a)/sizeof(a[0]);
SelectSort(a,len);
Print(a,len);
}
堆排序
堆创建:升序——>建大堆 降序——>建小堆
是一种处理大批量数据排序的算法,使用了大堆(或小堆)的关键字最大(或最小)的特征,建立堆进行排序,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。
(1)思想:
首先根据元素建堆,然后将堆的根节点取出(将根节点与最后一个节点进行交换),将前面len-1个节点继续进行堆调整的过程,然后再将根节点取出,这样一直到所有的节点都取出。
(2)复杂度:
堆排序单趟排序的时间复杂度为O(logN),需要排N次,所以堆排序的时间复杂度为O(N*logN)
空间复杂度O(1)
堆排序是一种不稳定的排序算法
(3)优缺点
优点:排序比较快
缺点:堆排序只能排序存储于数组中的元素,故在某些场合无法使用。
(4)代码实现:
void AdjustDown(int* a, size_t n, size_t i)
{
int parent = i;
int child = parent * 2 + 1;
while (child < n)
{
//找出孩子的最大值
if (child + 1 < n && a[child+1] > a[child])
child++;
if (a[child]>a[parent])
{
swap(a[child], a[parent]);
parent = child;
child = child * 2 + 1;
}
else
{
break;
}
}
}
void HeapSort(int* a,size_t n)
{
//构建一个堆
int* heap = new int[n];
for (size_t i = 0; i < n; i++)
{
heap[i] = a[i];
}
//向下调整
for (int i = (n-2)/2; i >= 0; --i)
{
AdjustDown(heap, n, i);
}
//堆排序的实质性部分
int end = n - 1;
//先将第一个元素和最后一个元素的值进行交换
//然后将最后一个元素不再视为堆内的内容,缩小排序范围
while (end>0)
{
swap(heap[0], heap[end]);
AdjustDown(heap, end, 0);
end--;
}
//打印
for (size_t i = 0; i < 10; i++)
{
cout << heap[i] << " ";
}
cout << endl;
}