直接插入排序是一种基于比较的排序算法,其主要思想是将待排序序列分为已排序和未排序两个部分,每次将未排序的元素从后往前依次插入到已排序的序列中,直到所有元素都被插入到已排序的序列中,从而得到一个有序的序列。
其动画演示为:
其代码实现为:
/**************************************
* 函数功能:对数组进行直接插入排序
* 函数参数:vector&data表示待排序数组
* 函数返回值:无
**************************************/
void insertSort(vector<int> &data)
{
// 获取数组长度
int len = data.size();
// 插入排序核心代码
for (int i = 1; i < len; ++i)
{
// 判断前一个元素是否小于当前元素
if (data[i - 1] < data[i]) continue;
// 保存当前元素
int temp = data[i];
// 哨兵元素
int k = i - 1;
// 判断哨兵元素是否大于当前元素
while (k >= 0 && temp < data[k])
{
data[k + 1] = data[k];
--k;
}
// 将当前元素插入到合适位置
data[k + 1] = temp;
}
}
直接插入排序的时间复杂度为 O(n^2)
,空间复杂度为 O(1)
。在排序过程中,如果待排序的序列已经近似有序,则直接插入排序的效率会很高,因为少需移动已排序部分的元素。但如果待排序序列的规模很大或者是随机分布的,那么直接插入排序的效果会不如其他高效率排序算法,如快速排序等。
折半插入排序(Binary Insertion Sort)是一种基于比较的排序算法,与直接插入排序类似,只是在查找元素插入位置时,采用了折半查找的方式,从而使得查找的平均时间复杂度降低到 O(log n)
级别。
相对于插入排序而言,虽然算法实现上需要执行二分查找,但是由于它减少了比较次数,因此在时间复杂度上有所优化。但是在实际应用中,对于小规模数据,插入排序可能比折半插入排序更加高效,因为对于小规模数据,折半插入排序反而会增加了一些不必要的开销。
其代码实现为:
/***************************************
* 函数功能:对数组进行折半插入排序
* 函数参数:
* 函数返回值:
****************************************/
void insertHalfSort(vector<int>&data)
{
// 获取数组长度
int n = data.size();
// 折半插入排序
for (int i = 1; i < n; i++)
{
// 临时保存数据
int temp = data[i];
// 折半插入的范围
int left = 0, righ = i-1;
// 折半比较
while (left <= righ)
{
int mid = (left + righ) / 2;
if (data[mid] > temp) righ = mid - 1;
else left = mid + 1;
}
// 移动数据
for (int j = i-1; j >= left ; j--)
data[j + 1] = data[j];
data[left] = temp;
}
}
折半插入排序的时间复杂度为 O(n^2)
,但相对于直接插入排序,它的平均时间复杂度要稍微低一些,为 O(n log n)
。在元素规模较大,且待排序序列相对有序时,折半插入排序的效率明显优于直接插入排序。值得注意的是,折半插入排序的空间复杂度为 O(1)
,是一个原地排序算法。
希尔排序(Shell Sort)是一种基于插入排序的快速排序算法。由于插入排序对于在已经排序较为有序的序列插入一个新元素时易产生最糟糕的情况,因此希尔排序的主要思想是通过先将整个待排序序列分成若干个子序列(按照一定的步长进行分组),并对每个子序列进行插入排序,从而使得原来的序列大部分有序。之后再对整个待排序序列进行一次插入排序,以此达到快速排序的目的。
希尔排序的过程如下:
例如:当我们需要对数据9,1,2 ,5,7,4,8,6,3,5进行排序时,其排序过程可为:
其代码实现为:
/***************************************
* 函数功能:对数组进行希尔排序
* 函数参数:vector&data-待排序数组
* 函数返回值:无
****************************************/
void shellSort(vector<int>&data)
{
int len = data.size();
// 增量变化
for (int dk = len / 2; dk >= 1; dk = dk / 2)
{
// 遍历数组
for (int i = dk + 1; i < len; ++i)
{
if (data[i] < data[i - dk])
{
int j, temp = data[i];
// 数组记录后移
for (j = i - dk; j >= dk && temp < data[j]; j -= dk)
data[j + dk] = data[j];
// 插入元素
data[j + dk] = temp;
}
}
}
}
希尔排序的时间复杂度较为复杂,取决于增量分布的具体方式,一般为O(n log n)
到 O(n^2)
。
冒泡排序是一种基本的排序算法,它的原理是比较相邻的两个元素,如果顺序不对则交换。这样一趟下来,最大的元素就排到了最后面,然后再针对剩下的元素重复此操作,直到所有元素都有序为止。其时间复杂度为O(n^2^)
。
其动态图解可为:
其代码实现为:
/***************************************
* 函数功能:对数组进行冒泡排序
* 函数参数:vector&data 待排序的数组
* 函数返回值:无
****************************************/
void bubbleSort(vector<int>&data)
{
// 获取数组大小
int len = data.size();
for (int i = 0; i < len - 1; i++) // 控制排序轮数
{
// 定义变量用于判断排序是否完成 提高排序效率
bool flag = true;
for (int j = 1; j < len - 1 - i; j++) // 控制排序次数
{
// 两数据不符合预设的顺序 应该发生交换
if (data[j] < data[j-1])
{
int temp = data[j - 1];
data[j - 1] = data[j];
data[j] = temp;
// 发生交换,排序未完成
flag = false;
}
}
// 判断排序是否完成
if (flag)
return;
}
}
这里为了避免不比较的循环,上述代码进行了改进,即添加标志位flag
,用于判断本趟for
是否发生交换,如果没有发生交换就说明排序已经完成了。
快速排序(Quicksort)是一种常用的排序算法,它的性能通常比冒泡排序和选择排序要好的多。快速排序采用分治法来实现,它的基本思想是将一个大问题分成小问题,然后再通过递归的方式解决。
其动态图解为:
快速排序中的划分操作,即将一个数组划分成两个子数组,其中一个子数组的所有元素都小于等于另一个子数组的所有元素。具体实现为选取第一个元素作为中枢(pivot),然后使用双指针法,将小于中枢的值放在左边,大于中枢的值放在右边,最终得到中枢的位置。具体代码实现可以见下:
/***************************************
* 函数功能:对数组进行快速排序的划分
* 函数参数:vector&data 待排序的数组
* 函数返回值:无
****************************************/
static int partition(vector<int>&data, int left, int right)
{
// 选取划分中枢
int tempKey = data[left];
while (left < right)
{
// 将数组右端小于中枢的值移动到左端
while (data[right] >= tempKey && right > left) --right;
data[left] = data[right];
// 将数组左端小于中枢的值移动到右端
while (data[left] <= tempKey && right > left) ++left;
data[right] = data[left];
}
// 循环跳出的条件左值就为中枢的位置
data[left] = tempKey;
return left;
}
下面这段代码实现了快速排序算法的递归过程。具体实现为:
/***************************************
* 函数功能:对数组进行快速排序
* 函数参数:vector&data 待排序的数组
* 函数返回值:无
****************************************/
void quickSort(vector<int>&data,int left,int right)
{
// 排序不合理
if (left >= right) return;
// 获取一次划分 并且将划分后最终结果保存 并传递给下一次
int temp = partition(data,left,right);
// 再次进行左半部分排序
quickSort(data,left,temp - 1);
// 再次进行右半部分排序
quickSort(data, temp + 1, right);
}
可以将这段代码与划分操作的实现结合起来,得到完整的快速排序算法实现。
合并排序,名字来源于小编。
这段代码实现的是归并排序中的合并操作。归并排序采用分治策略,将一个数组分成两个子数组,分别对这两个子数组进行排序,然后将排序后的子数组进行合并。具体实现为:
/***************************************
* 函数功能:对数组进行合并排序
* 函数参数:vector&data1 归并数组1 vector&data2 归并数组2
* 函数返回值:返回归并后的数组
****************************************/
vector<int> mergeSort(vector<int>&data1, vector<int>&data2)
{
// 获取带归并数据的大小
int len1 = data1.size(), len2 = data2.size();
// 返回结果
vector<int> res;
int i = 0, j = 0;
// 遍历两个数组 将数值更小的值放入结果集
while (i < len1 && j < len2)
{
// 数组1中数据更大
if (data1[i] > data2[j])
{
res.push_back(data2[j]);
j++;
}
else
{
res.push_back(data1[i]);
i++;
}
}
// 遍历未完成的数组
while (i < len1)
res.push_back(data1[i++]);
while (j < len2)
res.push_back(data2[j++]);
return res;
}
归并排序是一种经典的排序算法,它采用分治策略。将一个数组分成两个子数组,分别对这两个子数组进行排序,然后将排序后的子数组进行合并。
其动态图解为:
下面这段代码实现的是归并排序中的合并操作。具体实现为:
(上述的合并排序就源自此处)
其代码实现为:
/***************************************
* 函数功能:对数组进行合并排序
* 函数参数:vector&Array归并数组 int front归并排序的左起始位置 int mid归并排序的中间位置 int end归并排序的右终止位置
* 函数返回值:无
****************************************/
void Merge(vector<int> &Array, int front, int mid, int end) {
vector<int> LeftSubArray(Array.begin() + front, Array.begin() + mid + 1);
vector<int> RightSubArray(Array.begin() + mid + 1, Array.begin() + end + 1);
int idxLeft = 0, idxRight = 0;
LeftSubArray.insert(LeftSubArray.end(), numeric_limits<int>::max());
RightSubArray.insert(RightSubArray.end(), numeric_limits<int>::max());
for (int i = front; i <= end; i++) {
if (LeftSubArray[idxLeft] < RightSubArray[idxRight]) {
Array[i] = LeftSubArray[idxLeft];
idxLeft++;
} else {
Array[i] = RightSubArray[idxRight];
idxRight++;
}
}
}
下面这段代码实现的是归并排序算法的递归过程。具体实现为:
/***************************************
* 函数功能:对数组进行归并排序
* 函数参数:vector&Array归并数组 int front归并排序的左起始位置 int end归并排序的右终止位置
* 函数返回值:无
****************************************/
void MergeSort(vector<int> &Array, int front, int end) {
if (front >= end)
return;
int mid = (front + end) / 2;
MergeSort(Array, front, mid);
MergeSort(Array, mid + 1, end);
Merge(Array, front, mid, end);
}
可以将递归过程与合并操作的实现结合起来,得到完整的归并排序算法实现。
简单选择排序(Selection Sort)是一种简单直观的排序算法,它的基本思想是:每一轮从待排序的元素中选出最小(或最大)的一个元素,存放到序列的起始位置,直到全部待排序的元素排完为止。
其动态图解为:
具体实现为:
其代码实现为:
/***************************************
* 函数功能:对数组进行简单选择排序
* 函数参数:vector&data1 排序数组
* 函数返回值:无
****************************************/
void selectSort(vector<int>& date)
{
// 遍历选择
for (int i = 0; i < date.size(); ++i)
{
// 记录最小元素的位置
int min = i;
// 遍历查找i后面的元素
for (int j = i + 1; j < date.size(); ++j)
if (date[j] < date[min])
min = j;
// 判断当前元素是否为最小值
if (min != i)
{
// 不使用临时变量完成交换 但是注意数据是否会越界
date[min] = date[min] + date[i];
date[i] = date[min] - date[i];
date[min] = date[min] - date[i];
}
}
}
简单选择排序的时间复杂度为O(n^2)
,空间复杂度为O(1)
。由于每次选择最小元素时需要进行循环比较,因此不适合处理大量数据。可以将直接选择排序和其他排序算法的实现结合起来,得到更完整的排序算法实现。
至于折半插入排序,其平均时间复杂度为O(n^2^)
,最优时间复杂度为O(nlogn)
,最差时间复杂度为O(n^2^)
,是一种稳定的排序算法稳定。