1.插入排序
插入排序 复杂度t=O(n的平方)
将一个记录插入到已排好序的有序表中,从而到一个新的长度++的有序表
具体操作:
- 数i与数i-1比,如果小,则把数i放在data[0]中作哨兵
- 哨兵依次与数i-2及之前的数(以数j表示之)相比,如果哨兵小,则数j后移,否则覆盖在数j后面的位置上
- 注意,当–j一直到0的时候,data[0]
void insert_sort(int data[], int n) {//data[0] 没有使用
int i, j;
for (i = 2; i <= n; ++i) { // i从1开始
if (data[i] < data[i-1]) {
data[0] = data[i]; // 设置哨兵
for (j = i-1; data[0] < data[j]; --j) {
data[j+1] = data[j]; // 前面的覆盖后面的
}
data[j+1] = data[0]; // 永远都是被别人覆盖的放在左边
}
}
}//insert_sort
2. 冒泡排序
复杂度t=O(n的平方)
交换序列中相邻两个整数为基本操作
将被排序的记录数组data[0..n-1]垂直排列,每个记录data[i]看作气泡。
根据轻气泡不能在重气泡之下的原则,从上往下扫描数组data, 较重者下沉。
如此反复进行,重气泡依次下沉直到最后有序
void bubble_sort(int data[], int n) {
int i, j;
bool tag = true;
for (i = n-1; i > 0 && tag; --i) { // 代表需要排序的序列长度依次减少
tag = false;
for (j = 0; j < i; ++j) {
if (data[j] > data[j+1]) {
swap(data[j], data[j+1]);
tag = true;
}
}
}
}//bubble_sort
3.二分查找
对有序数组进行二分查找
/*
有序数组二分查找,非递归
return: 若找到则到返回相应的位置下标,否则返回-1
*/
int bsearch(int data[], int n, int key) {
int left = 0, right = n -1;
while (left <= right) {
int mid = (left + right) / 2;
if (data[mid] == key) {
return mid;
}
if (data[mid] > key) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
//递归实现
//data有序数组
int bsearch(int data[], int left, int right, int key) {
if (left > right) {
return -1;
}
int mid = (left + right) / 2;
if (data[mid] == key) {
return mid;
}
if (data[mid] < key) {
return bsearch(data, mid, right, key);
}
return bsearch(data, left, mid, key);
}
4. 快速排序
复杂度最坏O(n的平方),平均O(nlogn)**辅助存储O(logn)。
是对冒泡的一种改进,也是交换排序
思想:通过一趟排序将数组列分成两个独立的部分,其中一部分的值都比另一部分的小,则分别对这两部分继续进行快速排序,直到整个序列有序
具体操作:快排由两个函数构成,一个是partition根据枢轴将数据data[]分区,另一个是quick_sort,它先调用partition进行一次划分,然后对前后两个分区各自递归调用quick_sort进行快排。
/*
返回划分两区后,枢轴的位置
data[low...high]中,返回枢轴的位置,在它之前的元素值都不大于它,在它之后的都不小于它
*/
int partition(int data[], int low, int high) {
int pivot = data[low]; // 枢轴,总是第一个数,根据它来区分两组的。
while (low < high) {
while (low < high && data[high] >= pivot) { // 从high位置开始往前找,直到找到比pivot小的数
--high;
} //此时data[high]<pivot
data[low] = data[high]; //将data[high]存于data[low]中,此时data[high]为空位
while (low < high && data[low] <= pivot) { // low位置值与pivot比,低位下标++
++low;
} //此时data[low]>pivot
data[high] = data[low]; // data[low]存于data[high]中。此时data[low]为空位
}
//while结束时,data[low]一定是空位
data[low] = pivot;
return low; //返回的low值一定就是pivot在新数组中的下标
}
void quick_sort(int data[], int low, int high) {
if (low < high) {
int k = partition(data, low, high);
quick_sort(data, low, k-1);
quick_sort(data, k+1, high);
}
}// quick_sort
5. 希尔排序
先将整个待排记录分成若干个子序列,再进行直接插入排序
待全体基本有序时,再对全体进行一次直接插入排序
(子序列元素不一定相邻,而是相隔某个增量的记录组成的一个子序列)
void shell_insert(int data[], const int n, const int d) { // d是增量 data[0]没使用
int i, j, tmp;
// 对每个子序列进行插入排序
for (i = d; i <= n; i++) {
data[0] = data[i];
for (j = i-d; (j > 0) && (data[0] < data[j]); j-=d) { // 这里和普通的直接插入排序的区别就是必须要判断j>0,因为j每次递减的是d大小,哨兵可能拦截不住
data[j+d] = data[j];
}
data[j+d] = data[0];
}
}
/*
params:
data : 待排数组,数据存在data[1...n]中,data[0]为空位,留给哨兵
darr : 递增值d构成的数组。按照最后进行一次插入排序的要求,最后一个值是1
len_data : data的长度
len_darr : darr的长度
*/
void shell_sort(int data[], int darr[], const int len_data, const int len_darr) {
for (int i = 0; i < len_darr; ++i) {
shell_insert(data, len_data, darr[i]);
}
}//shell_sort
6. 简单选择排序
基本思想:每一趟在n-(i-1)个记录中选取值最小的记录作为有序序列的第i个元素。
void simple_select_sort(int data[], int n) {
int i, j, min_index;
for (i = 0; i < n; ++i) {
min_index = i; // min_index是当前最小元素的下标。存下标而不存变量值,免去频繁交换数据的效率问题
for (j = i+1; j < n; ++j) { // 注意i+1
if (data[j] < data[min_index]) {
min_index = j;
}
}
if (i != min_index) {
swap_int(data[i], data[min_index]);
}
}
} // simple_select_sort
7. 堆排序
最坏=平均=O(nlogn),不需要辅助存储,没有使用data[0]作暂存
将一个无序序列放到一个完全二叉树中。
调整为一个大顶堆(输出的时候就从大到小输出,序列从尾部向前伸展变成有序列)
输出堆顶后,重新调整为大顶堆
将大量的时间花在建堆上,适用于n较大的情况
/*
heap_adjust是将data调整为大顶堆。
已知data[start...end]除元素data[start]外均满足大顶堆的定义(父亲结点总是比孩子结点值大)
调整为大顶堆,就是让data[start]下沉到适当的位置上去。
初始假设data[n/2+1...n-1]已经满足大顶堆的定义(因为它们是叶子节点,没有孩子)。
初次调整时start为最后一个非叶子结点,即n/2(n是data的长度),start依次减小,直至为0。
*/
void heap_adjust(int data[], int start, int end) {
//一棵子树中,父结点与大孩子交换值(交换之后,为了保证大孩子这棵子树满足堆的定义,沿大孩子方向向下)
for (int child_index = start * 2; child_index <= end; child_index *= 2) {
if (child_index < end && data[child_index] < data[child_index+1]) {
++ child_index;
}// i现在是data[start]的较大孩子的下标
if (data[start] >= data[child_index]) {// data[start]与它的较大值的孩子比较;只有孩子比它大时,才作交换
break;
}
swap_int(data[start], data[child_index]); // 父结点与大孩子交换值
start = child_index; // 沿着较大孩子向下
}
}
/*
有序序列从后向前延伸.
*/
void heap_sort(int data[], int n) {
//初始调整为大顶堆,调整后data[0]为最大值
for (int i = n/2; i >= 0; --i) { // i从最后一个非叶子结点开始,倒序遍历
heap_adjust(data, i, n-1);
} //当前data是大顶堆
for (int i = n-1; i > 0; --i) {
swap_int(data[0], data[i]); //交换之后,data[i]及之后的元素是有序序列, data[0...i-1]除data[0]之外已满足大顶堆的定义
heap_adjust(data, 0, i-1); //让data[0]下沉到相应的位子上(持续向下跟大孩子作交换)。调整之后data[0...i-1]是个大顶堆。
}
}// heap_sort
8. 归并排序
最坏=平均=O(nlogn),辅助存储O(n)
初始序列可看成是n个有序的子序列,子序列长度为1,然后两两归并,得到长度为2的有序子序列。再两两归并。。。。
直到得到一个长度为n的一个有序列为止
这叫2路归并排序(使用递归实现,从大处着手,但是从递归到两两相邻时的深度时才开始交换)
要进行logn趟归并
void merge(int data[], int left, int mid, int right) {
//new一个辅助存储空间tmp数组, 将有序的tmp[left..mid]和tmp[mid+1..right]归并为有序的data[left...right]
int *tmp = new int[right - left + 1];
int i = left;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= right) {
if (data[i] <= data[j]) {
tmp[k++] = data[i++];
} else {
tmp[k++] = data[j++];
}
}
while (i <= mid) {
tmp[k++] = data[i++];
}
while (j <= right) {
tmp[k++] = data[j++];
}
// 复制回来
i = left;
k = 0;
while (i <= right) {
data[i++] = tmp[k++];
}
delete[] tmp;
}
void merge_sort(int data[], int left, int right) { // 最后结果放在在data中
if (left >= right) {
return;
}
int mid = (left + right) / 2;
merge_sort(data, left, mid);//对左半区进行归并排序
merge_sort(data, mid+1, right);//对右半区进行归并排序
merge(data, left, mid, right);//现在,左右两个半区都是有序的了,现在把两个有序的序列归并起来。
} //merge_sort