1. 插入排序
实现源码:
void InsertSort(int* arr, unsigned int len)
{
//前置条件判断
if (!arr || len <= 1)
{
return;
}
unsigned int i;
unsigned int j;
for (i = 1; i < len; i++)
{
j = i - 1;
int x = arr[i];
while (j >= 0 && arr[j] > x)
{
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = x;
}
}
性能分析:假设需要从小到大进行排序,则插入排序的最坏情形是原来的数组是按照从大到小的顺序排列的,这样需要的时间复杂度是:
插入排序的最好情形是原来的数组已经从小到大排好序了,则时间复杂度是:
接下来是插入排序的平均情况,其时间复杂度为:
最后,插入排序是稳定的。
2. 折半插入排序
在前面插入排序中,由于每次在插入的过程中,都需要与前面已排好序的元素进行比较,但是需要都进行比较吗?考虑到已经排好序这个特点,所以在比较的时候可以采用折半的方式,这里给出折半插入排序的实现源码:
void InsertHalfSort(int* arr, unsigned int len)
{
//前置条件判断
if (!arr || len <= 1)
{
return;
}
unsigned int i,j,low,mid,high,upper;
for (i = 1; i < len; i++)
{
int x = arr[i];
//初始化指针low、mid、high
low = 0;
high = i - 1;
mid = (low + high) / 2;
//通过折半查找查询应该插入的位置
while (low < high)
{
if (x == arr[mid])
{
break;
}
else if (x < arr[mid])
{
high = mid - 1;
}
else
{
low = mid + 1;
}
mid = (low + high) / 2;
}
if (arr[mid] <= x)
{
upper = mid + 1;
}
else
{
upper = mid;
}
for (j = i; j >= upper + 1; j--)
{
arr[j] = arr[j - 1];
}
arr[j] = x;
}
}
该排序算法的平均时间复杂度为O(nlgn),最好情形依旧为O(n),且是稳定的。
3. 希尔排序
希尔排序是对插入排序的又一次成功的改良,适用于大量数据的排序,且代码简洁:
void ShellSort(int* arr, int len)
{
int i, j, k;
k = len / 2;
while (k > 0)
{
for (i = k + 1; i < len; i++)
{
int x = arr[i];
j = i - k;
while (j >= 0 && arr[j] > x)
{
arr[j + k] = arr[j];
j = j - k;
}
arr[j + k] = x;
}
k = k / 2;
}
}
4. 归并排序
归并排序的关键是将两个已经排好序的数组合并成一个排序数组,以下是合并函数的实现源码:
//将两个有序的数组合并到一起
void Merge(int* arr, unsigned int begin, unsigned int mid, unsigned int high)
{
unsigned int len1 = mid - begin + 1;
unsigned int len2 = high - mid;
//构造临时数组存储合并的结果
int* tempArr = (int* )malloc((len1 + len2) * sizeof(int));
//申请内存空间失败,则返回
if (!tempArr)
{
return;
}
unsigned int i = 0;
unsigned int j = 0;
unsigned int k = 0;
while (i < len1 && j < len2)
{
if (arr[i + begin] > arr[j + mid + 1])
{
tempArr[k] = arr[j + mid + 1];
j++;
}
else
{
tempArr[k] = arr[i + begin];
i++;
}
k++;
}
if (i == len1)
{
while (j < len2)
{
tempArr[k] = arr[j + mid + 1];
k++;
j++;
}
}
if (j == len2)
{
while (i < len1)
{
tempArr[k] = arr[i + begin];
k++;
i++;
}
}
//将临时数组中的元素赋给原数组
for (i = 0; i < len1 + len2; i++)
{
arr[i + begin] = tempArr[i];
}
//释放空间
free(tempArr);
}
接下来只要进行递归归并排序即可:
//归并排序
void MergeSort(int* arr, unsigned int low, unsigned int high)
{
if (low < high)
{
unsigned int mid = (low + high) / 2;
MergeSort(arr, low, mid);
MergeSort(arr, mid + 1, high);
Merge(arr, low, mid, high);
}
}
归并排序的时间复杂度为O(nlgn),且是稳定的。
5. 快速排序
快速排序和归并排序一样,采用了分治的思想。快速排序是每次选择一个主元,然后将小于或等于主元的元素放到主元的左边,其余的放到主元的右边。以下是实现源码:
//交换两个整数
void Swap(int* x, int* y)
{
int temp = *x;
*x = *y;
*y = temp;
}
int Split(int* arr, int low, int high)
{
int i = low; //指针i始终指向轴点
int j = low + 1; //指针j用于遍历数组
while (j <= high)
{
if (arr[j] <= arr[i]) //如果小于或等于主元,则交换
{
i++; //注意,这里是先增加指针i,然后交换
Swap(&arr[i], &arr[j]);
}
j++;
}
Swap(&arr[i], &arr[low]);
return i;
}
//快速排序
void QuickSort(int* arr, int low, int high)
{
if (low >= high)
{
return;
}
int w = Split(arr, low, high);
QuickSort(arr, low, w - 1);
QuickSort(arr, w + 1, high);
}
快速排序的最坏情况是出现单边树的情况,此时快速排序的时间复杂度为O(n^2),其余情况下时间复杂度都是O(nlgn)。
6. 计数排序
如果已知序列中比元素x小的个数,则可以确定元素x在排序后的位置。这就是计数排序的基本思想,计数排序需要两个辅助数组B和C,B表示排好序的输出数组,C用于存储每个元素在原序列中比该元素小的个数,则有以下实现源码:
void CountSort(int* A, int* B, int* C, int len, int k)
{
int i = 0;
//将计数数组初始化为0
for (i = 0; i <= k; i++)
{
C[i] = 0;
}
//计数数组取值为A[i]的元素个数
for (i = 0; i < len; i++)
{
C[A[i]] = C[A[i]] + 1;
}
//计数数组取值为小于或等于i的元素个数,放在C[i]里面
for (i = 1; i < len; i++)
{
C[i] = C[i] + C[i - 1];
}
for (i = len - 1; i >= 0; i--)
{
B[C[A[i]]] = A[i];
C[A[i]] = C[A[i]] - 1;
}
}
计数排序的有点是线性时间和稳定性,但是由于计数排序的运行时间与输入参数的取值规模有关,所以在有些情况下会达到平方级时间,所以具有一定的局限性。
7. 基数排序
8. 桶排序
9. 堆排序