简图记录学习
目录
一、概念:
排序:将有n个记录的数据元素集合进行排序,使其记录的某个关键字满足递增或则递减的序列;
排序的稳定性:如果排序后关键字大小相同的记录和排序前顺序一样,称为稳定排序。
排序算法的性能要素:1、时间复杂度 2、空间复杂读 3、算法复杂性
排序名 | 排序类型 | 稳定性 | 时间复杂度 | 空间复杂度 | 选择参考 |
冒泡排序 | 交换排序 | 稳定 | 差:O(n^2) 平均:O(n^2) 最优:O(n) | O(1) | 少量数据 基本有序 |
选择排序 | 选择排序 | 稳定 | 差:O(n^2) 平均:O(n^2) 最优:O(n^2) | O(1) | |
直接插入排序 | 插入排序 | 稳定 | 差:O(n^2) 平均:O(n^2) 最优:O(n) | O(1) | 少量数据 基本有序 |
希尔排序 | 插入排序(优化) | 不稳定 | 差:O(n^2) 平均:O(nlogn)~O(n^2) 最优:O(n^1.3) | O(1) | 少量数据 低稳定 |
堆排序 | 选择排序(优化) | 稳定 | 差:O(nlogn) 平均:O(nlogn) 最优:O(nlogn) | O(1) | 大量数据 稳定性高 |
快速排序 | 交换排序(优化) | 不稳定 | 差:O(n^2) 平均:O(nlogn) 最优:O(nlogn) | O(nlogn)~O(n) | 大量数据 效率要求高 |
归并排序 | 归并排序 | 稳定 | 差:O(nlogn) 平均:O(nlogn) 最优:O(nlogn) | O(n) | 大量数据 空间约束小 |
二、排序算法实现分析
1、 冒泡排序-多轮遍历每轮连续比较相邻元素
从后往前连续比较相邻元素将小交换前移(让整个序列最小元素在最前端),然后对剩余序列继续重新相邻比较移动,直到整个序列有序。(优化方向:通过flag记录如果某一轮一次交换也没有,无需再继续比较);
/*1 冒泡排序*/
void bubble_sort(int a[], int n) {
int i, j, tmp;
for (i = 0; i < n-1; i++) { // 对比n-1轮,每轮从位置i开始(i前有序)
for (j = n-1; j > i; j--) { // 每轮从都从最后一个往前比较相邻
if (a[j-1] > a[j]) { //相邻小的上移动(每轮最小元素都能移到最前)
tmp = a[j];
a[j] = a[j-1];
a[j-1] = tmp;
}
}
}
}
/*1.2 冒泡排序 优化*/
void bubble_sort_better(int a[], int n)
{
int i, j, tmp;
int noSwichFlag=0;
for (i = 0; i < n-1 && (0 == noSwichFlag); i++) { //全部有序就跳出
for (j = n-1; j > i; j--) {
noSwichFlag = 1; // 优化点:如果有一轮没有任何移动,说明全部有序
if (a[j-1] > a[j]) {
tmp = a[j];
a[j] = a[j-1];
a[j-1] = tmp;
noSwichFlag = 0;
}
}
}
}
2、选择排序-多轮遍历每轮找到最小元素交换
找到序列中最小的元素交换到序列最前端,然后对剩余序列继续重复查找最小交换直到所有记录排序完成。
/*2 选择排序*/
void select_sort(int a[], int n) {
int i, j, tmp;
int min = 0;
for (i = 0; i < n-1; i++) { // 对比n-1轮,每轮从位置i开始(i前有序)
min = i;
for (j = i + 1; j < n; j++) { // 每轮从都从去找最小的一个数
if (a[j] < a[min]) {
min = j;
}
}
if (min != i) { // 把最小的这个数交换到最前
tmp = a[i];
a[i] = a[min];
a[min] = tmp;
}
}
}
3、直接插入排序-取新元素按大小插入其前有序区间
从前往后每次取一个记录按其大大小插入前面序列中,最后得到完整序列。
/*3 直接插入排序*/
void insert_sort(int a[], int n)
{
int i, j, tmp;
for (i = 1; i < n; i++) {
if (a[i] < a[i-1]) { // 如果当前元素 比 前(有序)区间 最后一个还小
// 把当前的元素插入有序位置(大于其元素均往后移一格)
tmp = a[i];
for (j = i-1; j >=0 && a[j] > tmp; j--) { //注意判断j边界
a[j+1] = a[j];
}
a[j+1] = tmp;
}
}
}
4、希尔排序-缩小增量分组优化的直接插入排序
对直接插入排序的改进版本,缩小增量排序。把记录按下标一定增量分组,每组安直接插入排序,然后减小增量重新分组排序直至增量为1。(如10个数据按5整量分组为:0和5、1和6、2和7。。。)
/*4 希尔排序*/
void shell_sort(int a[], int n)
{
int i, j, gap;
for (gap = n / 2; gap > 0; gap /= 2) { //gap为比较元素下标增量
for (i = gap; i < n; i++) {
if (a[i] < a[i - gap]) { // 注意插入排序下标变化按gap走
int tmp = a[i];
for (j = i - gap;j >= 0 && a[j] > tmp; j -= gap) {
a[j + gap] = a[j];
}
a[j + gap] = tmp;
}
}
}
}
5、堆排序-利用(大顶)堆优化的选择排序
利用堆进行排序,将序列构建为1个大顶堆(每个节点大于等于其左右孩子的完全二叉树),将堆顶点(整个堆最大值)与序列末尾元素交换,然后对除了最后元素的剩余序列重新构建大顶堆以此循环最终所有元素有序。
(注:完全二叉树一般节点从1编号开始,对于编号为i的节点左右孩子为2i和2i+1,为了方便排序,一般堆排序a[n]数组从a[1]开始,a[0]为无效元素)
void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
/* 堆的排序从下层逐步往上,heahAdjust将堆中编号s进行排序 */
void heapAdjust(int a[], int s, int n) {
int i, tmp = a[s];
for (i = 2 * s; i <= n; i *= 2) {
// 找到s 左右孩子最大的一个 i
if (i < n && a[i] < a[i+1]) {
i++;
}
// 如果s的值 大于 最大的孩子,说明找到其位置,break把s放入此位置
if (tmp >= a[i]) {
break;
}
// 如果s的值小于最大的孩子,把s和最大孩子交换(保证堆父节点最大),
// 然后 需要判段的位置s 更新 为其最大孩子位i, 继续往下找(找到一个比孩子都大的位置)
a[s] = a[i];
s = i;
}
a[s] = tmp;
}
/*5 堆排序*/
/*注意 堆排序为了方便完全二叉树编号计算,默认从a[1]开始排序,
视a[0]为无效位,当要排序10个数据时,应使用a[11]存储,n=10*/
void heap_sort(int a[], int n) {
int i;
// 初始化大顶堆(从最后一个父节点往前调整)
for (i = n / 2; i > 0; i--) {
heapAdjust(a, i ,n);
}
// 每次堆顶(最大)交换到尾部,剩余元素再堆排序然后交换堆顶
for (i = n; i > 1; i--) {
swap(&a[1], &a[i]); //每次将大顶堆顶(最大元素)交换到尾部
heapAdjust(a, 1 , i-1); //由于堆顶已交换,从堆顶重新排序(堆大小减1)
}
}
6、归并排序-利用分治思想的有序队列合并排序
利用归并思想,将n个记录视为n个每个长度为1的子序列,然后两两归并得到n/2个长度为2的序列,以此类推直至长度为n的序列;
void mergelist(int *listl,int listl_size,int *listr,int listr_size) {
int tmp[20];
int i=0, j=0, k=0;
// 把两个有序序列 按元素大小 选择 放入新的 临时队列
while (i < listl_size && j < listr_size) {
tmp[k++] = (listl[i] < listr[j]) ? listl[i++] : listr[j++];
}
// 把每放完的左或者右序列继续追加到尾部;
while (i<listl_size) tmp[k++]=listl[i++];
while (j<listr_size) tmp[k++]=listr[j++];
// 把调整后的有序序列 拷贝回去
for (k=0;k < listl_size + listr_size; k++) {
listl[k] = tmp[k];
}
}
/*6 归并排序*/
/*递归实现*/
void merge_sort(int a[], int n) {
//递归终止条件为序列长度为1(无需调整)
if (n > 1) {
//分为left和right两个序列
int *listl = a;
int listl_size = n / 2;
int *listr = a + listl_size;
int listr_size = n - listl_size;
// 递归排序左序列
merge_sort(listl, listl_size);
// 递归排序右序列
merge_sort(listr, listr_size);
// 合并两个有序序列
mergelist(listl, listl_size, listr, listr_size);
}
}
7、快速排序-利用分治思想的冒泡排序
利用冒泡排序的优化,使用分治思想。从序列中选取一个基数,将所有大于基数的数放到基数右边,小于放到基数左边,然后针对基数左边和右边的分区递归的重新进行基数选择和排序直到每个分区只剩一个;
优化方向:通过3数取中法选择基数;消除不必要的交换
void swap_pos(int a[], int low, int high) {
int tmp = a[low];
a[low] = a[high];
a[high] = tmp;
}
int Partion(int a[], int low, int high) {
int point = a[low]; // 取出第一个元素做基准点
while (low < high) {
// 从high往下找到第一个小于point的,进行交换
while(low < high && a[high] >= point) high--;
swap_pos(a, low, high);
// 从low往上找到第一个大于point的,进行交换
while(low < high && a[low] <= point) low++;
swap_pos(a, low, high);
}
return low;
}
/* 快速排序优化:三数取中法优化基准点选择 */
int Partion_better(int a[], int low, int high) {
int point = a[low];//取出第一个元素做基准点
int m = low + (high - low)/2;
if (a[low] > a[high])swap_pos(a, low, high);
if (a[m] > a[high])swap_pos(a, m, high);
if (a[m] > a[low])swap_pos(a, m, low);
point = a[low];
while (low < high) {
//从high往下找到第一个小于point的,进行交换
while(low < high && a[high] >= point) high--;
swap_pos(a, low, high);
//从low往上找到第一个大于point的,进行交换
while(low < high && a[low] <= point) low++;
swap_pos(a, low, high);
}
return low;
}
void Qsort(int a[], int low, int high) {
if (low < high) {
int point = Partion(a, low, high);// 定位基准点
// point = Partion_better(a, low, high); // 优化定位基准点
Qsort(a, low, point - 1); // 从最低到point-1
Qsort(a, point + 1, high); // 从point+1到最高
}
}
/*7 快速排序*/
void quick_sort(int a[], int n)
{
Qsort(a, 0, n-1);
}