插入排序
算法步骤
- 假设待排序列存储在 A r r [ 0 : n ] Arr[0:n] Arr[0:n]当中
- 循环 n − 1 n-1 n−1次,每次使用顺序查找,得到 A r r [ i ] Arr[i] Arr[i]在有序序列 A r r [ 0 : i ] Arr[0:i] Arr[0:i]当中的插入位置,然后将其插入到有序序列当中,重复操作直到所有元素都插入到有序序列( A r r [ 0 : n ] Arr[0:n] Arr[0:n]有序)
算法实现
时间复杂度: O ( n 2 ) O(n^2) O(n2);空间复杂度: O ( 1 ) O(1) O(1),稳定排序
void insertSort(vector<int>& arr)
{
for(int i = 1;i<arr.size();i++)
{
int tmp = arr[i];
int j = i-1;
while((j>=0) && (tmp<arr[j]))
{
arr[j+1] = arr[j];
j--;
}
arr[j+1] = tmp;
}
}
折半插入排序
与普通的插入排序思想是相同的,但是因为查找要插入位置是在有序序列中查找,所以使用二分查找来减少比较次数,但移动次数不变,所以时间复杂度、空间复杂度相同
希尔排序
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本,从“减少记录个数”和“序列基本有序”两个方面对其进行了改进。
算法步骤
希尔排序通过某个增量将数组元素划分为若干组,然后分组进行插入排序,随后逐步缩小增量,继续按组进行插入排序操作,直至增量为1。
如图,第一趟间隔 d 1 = 5 d_1=5 d1=5,原始序列被分成 { 49 , 13 } 、 { 38 , 27 } \{49,13\}、\{38,27\} {
49,13}、{
38,27}等五组,在组内插入排序,得到了第一趟排序的结果,
然后第二趟间隔为 d 2 = 3 d_2=3 d2=3,全部记录分成3组,组内插入排序得到第二次的结果
第三趟增量为 d 3 = 1 d_3=1 d3=1,直接对整个序列插入排序,得到最终结果
算法实现
void shellInsertSort(vector<int>& arr,int dk)
{
for(int i = dk;i<arr.size();i+=dk)//从0号开始,下一个是dk号
{
int tmp = arr[i];
int j = i-dk;//从上一个
while((j>=0) && (tmp<arr[j]))
{
arr[j+dk] = arr[j];
j-=dk;
}
arr[j+dk] = tmp;
}
}
void shellSort(vector<int>& arr,vector<int> dks)
{
for(int i = 0;i<dks.size();i++)
{
shellInsertSort(arr,dk[i]);
}
}
算法分析
当增量大于1时,记录是跳跃式的移动的,从而最后一趟增量为1的排序时,序列已经基本有序,只需要少量比较和移动就可完成排序,所以希尔排序的时间复杂度要比直接插入排序低,但具体分析还是一个尚未解决的难题。
希尔排序空间复杂度和直接插入排序相同
希尔排序是不稳定的排序方法
冒泡排序
冒泡排序的思想是两两比较相邻元素,如果发生逆序则交换,从而让关键字小的元素像“气泡”上浮
算法步骤
- 假设待排序列存储在 A r r [ 0 : n ] Arr[0:n] Arr[0:n]中,首先比较第一个元素和第二个元素,若为逆序则交换,然后比较第二个和第三个,以此类推直到第n-1个和第n个比较完,完成一次冒泡过程
- 然后进行第二次冒泡过程,第一趟已经使最大的元素“沉底”,所以只对前n-1个元素进行冒泡
- 直到某一趟冒泡过程没有进行过交换操作,说明已经达到排序要求,完成排序
图片来源:https://www.runoob.com/w3cnote/bubble-sort.html
算法实现
void bubbleSort(vector<int>& arr)
{
int n = arr.size();
bool swapFlag = 1;
while((n>0) && (swapFlag==1))
{
swapFlag = 0;
for(int i = 0;i<n;i++)
{
if(arr[i]>arr[i+1]) //降序改为小于
{
int t = arr[i];
arr[i] = arr[i+1];
arr[i+1] = t;
swapFlag = 1;
}
}
n--;
}
}
算法分析
最好情况下只需比较 n − 1 n-1 n−1次,无需移动元素
最坏情况下,需要 n − 1 n-1 n−1次冒泡,总的比较次数为 n ( n − 1 ) / 2 ≈ n 2 / 2 n(n-1)/2 \approx n^2/2 n(n−1)/2≈n2/2,总的交换次数: 3 n ( n − 1 ) / 2 ≈ 3 n 2 / 2 3n(n-1)/2 \approx 3n^2/2 3n(n−1)/2≈3n2/2
所以平均情况下,比较次数和移动次数分别约为 n 2 / 4 , 3 n 2 / 4 n^2/4,3n^2/4 n2/4,3n2/4,故时间复杂度为 O ( n 2 ) O(n^2) O(n2)
空间复杂度为 O ( 1 ) O(1) O(1)
冒泡排序是稳定排序
算法平均时间性能由于移动元素次数多,所以比直接插入排序要差
快速排序
快速排序是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。
算法步骤
- 从序列中挑出一个元素作为枢轴(通常取第一个)
- 重新排序,所有比枢轴小的元素放在枢轴前面,所有元素比枢轴大的放在枢轴的后面(相同的数可以到任一边)
- 递归地把小于枢轴元素的子序列和大于枢轴元素的子序列排序;
其中一次快排的操作如下:
- 选择枢轴,设置两个指针low和high分别指向序列的下界和上界
- 从序列最右侧向左搜索,找到第一个小于枢轴的元素,将其移动到low处
- 然后从最左侧向右搜索第一个大于枢轴的元素,将其移动到high处
- 重复2、3,直到low与high相等为止,此时low(或high)所在位置即为这次排序枢轴的最终位置,左右为两个子序列
算法实现
int Paritition(vector<int>& arr, int low, int high)
{
int pivot = arr[low];
while (low < high)
{
while (low < high && arr[high] >= pivot)
{
--high;
}
arr[low] = arr[high];
while (low < high && arr[low] <= pivot)
{
++low;
}
arr[high] = arr[low];
}
arr[low] = pivot;
return low;
}
void QuickSort(vector<int>& arr, int low, int high)
{
if (low < high)
{
int pivot = Paritition(arr, low, high);
QuickSort(arr, low, pivot - 1);
QuickSort(arr, pivot + 1, high);
}
}
算法分析
理论上可以证明,平均情况下,快速排序的时间复杂度为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)
快速排序是递归的,执行行时需要有一个栈来存放相应的数据。最大递归调用次数与递归树的 深度一致,所以最好情况下的空间复杂度为 o ( l o g 2 n ) o(log_2n) o(log2n),最坏情况下为 O ( n ) O(n) O(n)
快速排序是不稳定排序
参考
严蔚敏 李冬梅 吴伟民 《数据结构(C语言版)(第二版)》