原地排序算法,是特指空间复杂度是O(1)的排序算法。
排序算法的稳定性,指的是如果待排序的序列中存在值相等的元素,经给排序之后,相等元素之间原有的先后顺序不变。
排序算法test代码
int main() {
int a[10] = { 12,54,69,35,85,6,75,11,65,55 };
cout << "原数组为:";
for (int i = 0;i < 10;++i)
cout << a[i] << ' ';
cout << endl << endl;
//选择合适的排序算法
CountingSort(a, 10);
cout << "排序后为:";
for (int i = 0;i < 10;++i)
cout << a[i] << ' ';
cout << endl;
system("pause");
return 0;
}
1冒泡排序(Bubble Sort)
第一,冒泡排序是原地排序算法吗?
冒泡排序不需要额外的存储空间,它的空间复杂度为O(1),是一个原地排序算法。
第二,冒泡排序是稳定的排序算法吗?
在冒泡排序中,只有a[j]<a[j-1]才会交换顺序,所以相同大小的元素在排序前后不会改变顺序,所以原地冒泡是稳定的排序算法。
第三,冒泡排序的时间复杂度是多少?
最好情况下的时间复杂度是O(n),最坏情况下的时间复杂度是O(n方);平均情况下的时间复杂度是O(n方)
void Bubble_Sort(int a[], int n) {
if (n <= 1)
return;
for (int i = 1;i < n;++i) {
bool flag = false;
for (int j = 1;j < n-i+1;++j) {
if (a[j] < a[j - 1]) {
swap(a[j], a[j - 1]);
flag = true;
}
}
//假如没有交换说明已经有序了,不需要再排序了,提前退出
if (flag = false)
break;
}
}
2插入排序(Insertion Sort)
一个有序的数组,我们往里面添加一个新的数据,只要在原来的有序数组中找到新数据应该存放的位置即可。
第一,插入排序是原地排序算法吗?
插入排序同样不需要额外的存储空间,因此它的空间复杂度为O(1),所以插入排序是一个原地排序算法。
第二,插入排序是稳定的排序算法吗?
因为插入排序中只有当待插入数据value<a[j]的时候才需要将数据后移,因此相同的数据在数组中的先后顺序不会发生改变,因此插入排序是一个稳定的排序算法。
第三,插入排序的时间复杂度是多少?
平均时间复杂度O(n方)
void Insertion_Sort(int a[], int n) {
for (int i = 1;i < n;++i) {
int temp = a[i];
int j;
for (j = i;j > 0;--j) {
//数据后移
if (temp < a[j - 1])
a[j] = a[j - 1];
else
break;
}
//此时的j就是temp这个值应该存放的位置
a[j] = temp;
}
}
3选择排序(Selection Sort)
选择排序类似插入排序,但是插入排序是将数据后移直到找到新数据插入的位置,而选择排序是每次从未排序的区间中找到最小值,将其放到已排序区间的末尾。
选择排序是原地排序算法
选择排序不是稳定的排序算法,因为选择排序每次都要找剩余未排序元素中的最小值,并和前面已排序数据的后一个数据进行交换,这个交换就可能破坏了稳定了。
例如5,8,5,2,9,第一次找到最小元素2,与第一个5交换,那么稳定性就被破坏了。
平均时间复杂度为O(n方)。
void Selection_Sort(int a[], int n) {
for (int i = 0;i < n;++i) {
int min_val = INT_MAX;
int min_index;
for (int j = i;j < n;++j) {
if (a[j] < min_val) {
min_val = a[j];
min_index = j;
}
}
swap(a[i], a[min_index]);
}
}
4归并排序(Merge Sort)
归并排序的原理
归并排序的核心思想是先把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排序好的两部分合并在一起,这样整个数组就都有序了。也就是分而治之的思想。
分治这种解决问题的思想需要用到递归。
第一,归并排序是稳定的排序算法吗?
代码中a[i]<=a[j],先将排在前面的a[i]放进排序的数组temp中,所以排序后的数组,相同大小数据的先后顺序不会发生变化,即归并排序是稳定的排序算法。
第二,归并排序是原地排序算法吗?
归并排序不是原地排序算法,因为在并的过程中需要用到temp数组来保存排序后的数据,排序好以后再将temp拷贝至原数组中,temp就是额外的存储空间。空间复杂度为O(n)。
第三,归并排序的时间复杂度是多少?
无论是最好情况、最坏情况,还是平均情况,时间复杂度都是O(nlogn)。
//---------------归并排序------------------
void merge(int a[], int p, int q, int r) {
//分开两个区间的起点i,j,临时数组起点k
int i = p, j = q + 1, k = 0;
//临时数组用来保存排序后的数组
int* temp = new int[r - p + 1];
while (i <= q && j <= r) {
if (a[i] <= a[j])
temp[k++] = a[i++];
else
temp[k++] = a[j++];
}
//剩余数据存放到temp中
int start = i, end = q;
if (j <= r) {
start = j;
end = r;
}
while (start <= end)
temp[k++] = a[start++];
//将数据拷贝到a中,一定要有等号,因为有r-p+1个数据
for (int c = 0;c <= r-p;++c) {
a[p+c] = temp[c];
}
}
void merge_sort_c(int a[], int p, int r) {
//终止条件
if (p >= r)
return;
//分割点q
int q = (p + r) / 2;
//分
merge_sort_c(a, p, q);
merge_sort_c(a, q + 1, r);
//并
merge(a, p, q, r);
}
void merge_sort(int a[], int n) {
merge_sort_c(a, 0, n - 1);
}
//------------------------------------------
5 快速排序(Quick Sort)
快速排序的原理
快排用的也是分治思想,但是思路完全不一样。快排的思想是,如果要排序数组中下标从p到r之间的一组数据,我们选择p到r之间的任意一个数据作为pivot。一般选最后一位作为pivot。
我们遍历p到r之间的数据,将小于pivot的放在左边,将大于pivot的放到右边,将pivot放到中间。经过这一步骤之后,数组p到r之间的数据就被分成了三个部分,前面p到q-1之间都是小于pivot的,中间是pivot,后面的q+1到r之间是大于pivot的。
根据分治、递归的处理思想,我们可以用递归排序下标从p到q-1之间的数据和下标从q+1到r之间的数据,直到区间缩小为1,就说明所有的数据都有序了。
快速排序的时间复杂度和归并排序一样,是O(nlogn)。
6 计数排序(Counting Sort)
void CountingSort(int a[], int n) {
if (n <= 1)
return;
//桶的最大值
int max = a[0];
for (int i = 0;i < n;++i) {
if (a[i] > max)
max = a[i];
}
//初始化桶,桶的范围是0-max
int* c = new int[max + 1];
for (int i = 0;i <= max;++i)
c[i] = 0;
//计算数组a中小于等于c的下标的数据的个数
for (int i = 0;i < n;++i)
c[a[i]]++;
for (int i = 1;i <= max;++i)
c[i] += c[i - 1];
//开始计数排序,这一步是关键
int* r = new int[n];
for (int i = n - 1;i >= 0;--i) {
int index = c[a[i]] - 1;
r[index] = a[i];
c[a[i]]--;
}
//排序好的数组r拷贝到数组a
for (int i = 0;i < n;++i)
a[i] = r[i];
}