一、冒泡排序
此算法从小到大排序的基本原理是:每一趟排序讲待排续空间中每一个元素与后一个进行比较,若存在小于关系,则进行冒泡(交换),一趟排序下来以后,待排序空间中的最后一个元素最大。
第 1趟排序时待排序下标空间为[0..N-1],从 a[o]到 a[N-1-1]依次与其后相邻元素比较,若大于,则交换。这样,第 1 趟排序之后,保证 [N-1]最小。
第 k 趟排序时待排序下标空间为[0..N-k],从 a[o]到 [N-k-1]依次与其后相邻元素比较,若小于,则交换。这样,第 k 趟排序之后,保证 a[N-k]最小。采用此算法重复地走访要排序的数列,一次比较两个元素,如果它们的顺序错误,就把它们交换过来。
借助从网上流传最广的图片理解。
最坏时间复杂度 | 最优时间复杂度 | 时间复杂度 | 空间复杂度 |
---|---|---|---|
o(n^2) | o(n^2) | o(n^2) | o(1) |
1、普通的冒泡
void BubbleSort(int* arr, int n)
{
for (int i = 0; i < n - 1; i++)
{
for (int j = 0; j < n - i - 1; j++)
{
if (arr[j] > arr[j + 1]) { // 相邻元素两两对比
int temp = arr[j + 1]; // 元素交换
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
}
2、优化的冒泡
void BubbleSort(int* arr, int n)
{
for (int i = 0; i < n - 1; i++)
{
int flag = 0;
for (int j = 0; j < n - i - 1; j++)
{
if (arr[j] > arr[j + 1]) { // 相邻元素两两对比
int temp = arr[j + 1]; // 元素交换
arr[j + 1] = arr[j];
arr[j] = temp;
flag++;
}
}
if (flag == 0)break;
}
}
这个函数的区别在于上述函数添加了一个优化的标志位(flag)。这个标志位用于判断在一轮冒泡排序中是否发生了元素交换,即是否有逆序对存在。如果在某一轮排序中没有发生逆序对,说明数组已经是有序的,可以提前结束排序过程。
这种优化可以有效减少排序的次数,当待排序的数组本身已经部分有序时,可以大幅度减少排序的时间复杂度。而第一个函数则是每一轮都需要进行完整的比较和交换操作,无论数组的有序程度如何。
综上所述,第二个函数(添加了标志位的冒泡排序)相对于第一个函数(普通的冒泡排序)在某些情况下具有更好的性能和效率。
void BubbleSort(int num[], int size)
{
int i, j, k, up, down, tmp, flag;
flag = -1;
up = size - 1;
down = 0;
for(i = size - 1; i > 0; i--)//从前往后排
{
for(j = down; j < up; j++)
{
if(num[j] > num[j+1])
{
tmp = num[j];
num[j] = num[j+1];
num[j+1] = tmp;
flag = j;//取出从前往后的下标,在下标之后的数都已经排好序
}
}
if(flag == -1)
{
break;
}
up = flag;//取出flag的值
flag = -1;//重置flag
for(k = up; k > down; k--)
{
if(num[k] < num[k-1])
{
temp = num[k];
num[k] = num[k-1];
num[k-1] = temp;
flag = k;//取出从后往前的下标,在下标之前的数都已经排好序
}
}
if(flag == -1)
{
break;
}
down = flag;//取出flag的值
flag = -1;//重置flag
}
}
这段代码实现的是一种改进的冒泡排序算法,其主要思想是同时从前往后和从后往前进行排序。这种改进的冒泡排序算法通过同时从两个方向进行排序,可以更快地将较大或较小的元素移动到正确的位置,从而提高排序效率。
二、选择排序
选择排序是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找最小(大)元素,将其放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。
1、普通的选择
void SelectSort(int* arr, int n)
{
for (int i = 0; i < n - 1 ; i++)
{
int min = i;
for (int j = i + 1; j < n; j++)
{
if (arr[j] < arr[min])
{
min = j;
}
}
if (arr[i] != arr[min])
{
Swap(&arr[i],&arr[min]);
}
}
}
2、优化的选择
void SelectSort(int* arr, int n)
{
int begin = 0, end = n - 1;
while (begin < end)
{
int max = begin, min = begin;
for (int i = begin+1; i <= end; i++)
{
if (arr[i] > arr[max])
max = i;
if (arr[i] < arr[min])
min = i;
}
Swap(&arr[begin], &arr[min]);
if (begin == max)
max = min;
Swap(&arr[end], &arr[max]);
begin++;
end--;
}
}
未优化的代码使用两层循环来实现选择排序。外层循环从索引0开始,依次遍历数组中的元素,内层循环从外层循环位置的下一个索引开始,找到未排序部分的最小元素,并将其索引赋值给变量min。然后,通过交换外层循环位置的元素和最小元素,将最小元素放置在已排序部分的末尾。这样,每一次外层循环结束时,已排序部分的长度会增加一个元素。
优化的代码使用了双指针的方式来实现选择排序。它使用begin和end两个指针分别指向待排序部分的起始和结束位置。在每一轮迭代中,通过两个嵌套的循环分别找到未排序部分的最大元素和最小元素的索引,并将它们分别赋值给变量max和min。然后,通过交换起始位置的元素和最小元素,将最小元素放置在已排序部分的末尾;再次交换结束位置的元素和最大元素,将最大元素放置在已排序部分的开头。接着,更新begin和end指针,缩小待排序部分的范围。
这两段代码的核心思想相同,都是在每一轮迭代中选择未排序部分的最小(或最大)元素,并将其放置在已排序部分的末尾(或开头)。它们的时间复杂度都为O(n^2),其中n为数组的长度。不同之处在于具体实现方式和使用的变量名不同,但最终的排序结果是相同的。
三、插入排序
此算法从小到大排序的基本原理是:从第二个元素开始,将其视为已排序序列。将当前元素与已排序序列中的元素进行比较,找到合适的位置插入。插入操作涉及将较大元素向右移动,为新元素腾出空间,直到所有元素都被插入到正确的位置。
具体步骤如下:
- 从第二个元素开始,将其保存在一个临时变量中。
- 向前遍历已排序序列,如果当前元素大于临时变量,将当前元素后移。
- 如果找到小于或等于临时变量的位置,将临时变量插入到该位置。
- 重复步骤 1 到 3,直到所有的元素都被插入到正确的位置。
借助从网上流传最广的图片理解。
1、普通的插入
void InsertSort(int arr[], int n) {
int i, key, j;
for (i = 1; i < n; i++) {
key = arr[i];
j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
2、优化的插入
int BinarySearch(int* arr, int key, int low, int high) {
int mid = -1;
while (low <= high) {
mid = left+(right-left) / 2;
if (key <= arr[mid])
high = mid - 1;
else
low = mid + 1;
}
return low;
}
void binInsertSort(int* arr, int n) {
for (int i = 1; i < n; i++) {
int key = arr[i];
int loc = BinarySearch(arr, key, 0, i);
if (loc != i)
{
for (int j = i - 1; j >= loc; j--)
arr[j + 1] = arr[j];
arr[loc] = key;
}
}
}
使用二分查找改进插入排序,使之最坏情况下时间复杂度为Θ(nlgn)。
此种方法实现了使用二分查找来确定插入元素的位置,从而优化插入排序的过程。具体分析如下:
1.函数binarySearch():该函数用于确定元素key在已排序数组arr中应该插入的位置。它通过不断将待查找区间缩小为原来的一半来进行查找,直到最后low>high时停止,此时low即为要插入的位置。在每一次比较时,通过与中间元素比较来确定key所属的子区间。
2.函数binInsertSort():该函数实现了插入排序过程,对数组arr进行排序。在每一次迭代中,首先将待插入的元素key查找其应当插入的位置,然后再移动其他元素,将key插入到正确的位置上。
总的来说,该算法通过减少比较次数和移动元素的次数,从而减少了插入排序的时间复杂度。然而,在最坏的情况下,插入排序仍然需要O(n^2)次比较和移动操作,因此在处理大量数据时,比如超过10000个数据,还是需要使用更高效的排序算法。