好久不看基础算法了。。。今天花点时间重新温习一下。
主要实现了下面几个排序算法:
- 插入排序
- 选择排序
- 归并排序
- 冒泡排序
- 堆排序
- 快速排序
事实证明,一段时间不用,记忆就会出现偏差。今天写了一下,感觉对这几个算法又多了一些理解~
程序实现
//
// main.cpp
// sort_algorithm_learning
//
// Created by cslzy on 16/8/24.
// Copyright © 2016年 CY. All rights reserved.
//
#include <iostream>
#include <vector>
using namespace std;
void swapa(int *a, int *b);
void printa(int a[], int al);
void insert_sort(int a[], int al);// 插入排序是每次形成一个相对有序的子集
void select_sort(int a[], int al);
void merge_sort(int a[], int left, int right);
void bubble_sort(int a[], int al);// 与插入排序相比,冒泡排序每次找到一个绝对位置的数据(这个数值的最终位置)
void heap_sort(int a[], int al);
void max_heapify(int a[], int root, int end);
void quick_sort(int a[], int left, int right);
int quick_one_shot(int a[], int left, int right);// return the pivot
int main(int argc, const char * argv[]) {
// insert code here...
std::cout << "Hello, World!\n";
// 使用 python 生成一个随机数组 :np.random.randint(10, size=[1,100])
int a1[] = {21, 37, 93, 8, 66, 58, 76, 91, 42, 84};
int a2[] = {49, 67, 77, 81, 23, 13, 28, 80, 5, 17, 90, 67, 45, 5, 37, 63, 25, 8, 4, 35, 50, 13, 42, 5, 83, 85, 74, 31, 11, 84, 46, 56, 87, 42,
71, 79, 59, 75, 4, 10, 23, 44, 6, 15, 95, 90, 75, 14, 44, 81, 36,
75, 30, 29, 62, 63, 98, 78, 51, 66, 56, 75, 77, 50, 41, 77, 44, 38,
19, 7, 29, 9, 52, 88, 92, 89, 80, 82, 67, 63, 92, 62, 44, 81, 17,
69, 17, 96, 78, 86, 67, 95, 73, 59, 62, 74, 58, 83, 34, 78};
vector<int> arr;
for(auto v: a1) // 在这里改变想测试的数组名称
arr.push_back(v);
int al = (int)arr.size();
int b[al];
for(int i = 0; i < al; i++) b[i] = arr[i];
int s = 5;
string algorithm[] = {"insert sort", "select sort", "merge sort", "bubble sort"
,"heap sort"
,"quick sort"};
cout<<"before sort"<<endl;
printa(b, al);
switch(s)
{
case 0:// insert sort
{
//int al = sizeof(a) / sizeof(*a);
insert_sort(b, al);
break;
}
case 1: // select sort
{
select_sort(b, al);
break;
}
case 2: // merge sort
{
cout<<"merge sort"<<endl;
merge_sort(b, 0, al - 1);
break;
}
case 3: // bubble sort
{
cout<<algorithm[3]<<endl;
bubble_sort(b, al);
break;
}
case 4:
{
cout<<algorithm[4]<<endl;
heap_sort(b, al);
break;
}
case 5:
{
cout<<algorithm[5]<<endl;
quick_sort(b, 0, al - 1);
break;
}
default:
cout<<"no optional choice"<<endl;
break;
}
cout<<"result after "<<algorithm[s]<<" sort:"<<endl;
for(auto i: b)
cout<<i<<" ";
cout<<endl;
return 0;
}
void quick_sort(int a[], int left, int right)
{
if(left >= right) return;
int p = quick_one_shot(a, left, right);
// cout<<"separate by "<<a[p]<<endl;
// for(int i = left; i < right; i++)
// cout<<a[i]<<" ";
// cout<<endl;
quick_sort(a, left, p - 1);
quick_sort(a, p + 1, right);
// 快速排序
// 平均和最好情况下的时间复杂度是O(nlogn),最优是在每次选择的轴点能比较均匀地分开数组
// 最坏是在升序或降序的时候,为O(n^2)
// 不稳定
// 空间复杂度,最好是O(nlogn),最坏是O(n^2)
// n较大时,比较好。
}
int quick_one_shot(int a[], int left, int right)
{
int p = left;//选择首元素当做第一个轴点
int temp = a[left];
while(left < right)
{
while(left < right && a[right] > temp) right--; // 找到右为第一个比轴点大的元素
// 交换
if(left < right)
{
a[p] = a[right];
p = right;
}
while(left < right && a[left] < temp) left++;
if(left < right)
{
a[p] = a[left];
p = left;
}
}
a[p] = temp;
return p;
}
void heap_sort(int a[], int al)
{
// 建一个大根堆
for(int i = (al - 1 - 1) / 2; i >= 0; i--)
max_heapify(a, i, al - 1);
cout<<"排序过程"<<endl;
// 每次输出一个结点,并将剩下的结点排序
for(int i = al - 1; i > 0; i--)
{
printa(a, al);
swapa(&a[0], &a[i]);
max_heapify(a, 0, i - 1);
}
// 堆排序 : 我原来印象中的堆排序一直都是错的。。。
// 预处理,n/2个结点,最大深度是logn(2为底)
// (n - 1)次调整,每次的最大深度是logn(2为底)
// 时间复杂度均为O(nlogn)
// 空间复杂度O(1)
// 不稳定
// 适用于n较大的情况
// 我下面这个方法每次都把第一个非叶了结点到根结点的所有结点与其子结点比较一遍,
// 找出一个最大值,显然这个方法并不是堆排序。
// for(int i = al - 1; i > 0; i--)
// {
// for(int j = (i - 1) / 2; j >= 0; j--)
// {
// int son = j * 2 + 1; // left child, must exists.
// if(a[j] < a[son]) swapa(&a[j], &a[son]);
//
// son++; // right child
// if(son <= i && a[j] < a[son]) swapa(&a[j], &a[son]);
// }
// swapa(&a[0], &a[i]);
printa(a, al);// 输出每次排序后的结果,用于检查。
// }
// 堆排序
//
}
void max_heapify(int a[], int root, int end)
{
int parent = root;
int son = parent * 2 + 1; // first son
while(son <= end)
{
if(son + 1 <= end && a[son] < a[son + 1]) son++; // 判断一下有没有右孩子
if(a[parent] < a[son])
{
swapa(&a[parent], &a[son]);
parent = son;
son = parent * 2 + 1;
}
else
return;
}
}
void bubble_sort(int a[], int al)
{
for(int i = al; i > 0; i--)
{
int flag = true;// 判断条件
for(int j = 0; j < i - 1; j++)
{
if(a[j] > a[j + 1]) // 逐个比较,没有跳过的,所以是稳定算法。
{
flag = false;
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
if(flag) i = 0; // 如果在一个子遍历过程中,未发现逆序,则终止。
}
// 冒泡排序
// 可以是稳定的算法。
// 最好时间复杂度O(n), 平均和最坏都是O(n^2)。
// 空间复杂度O(1)
// 适用于n较小。这里说明一下,初始输入相对有序应该也是有帮助的。
}
void merge_sort(int a[], int left, int right)
{
if(left >= right) return;
int mid = (left + right) / 2;
int len = right - left + 1;
int l = left;
int r = mid + 1;
// merge left and right subarray
merge_sort(a, left, mid);
merge_sort(a, mid + 1, right);
int k = 0;
int temp[len];
while(k < len)
{
// 下面这两句没有考虑到一半读完了,而另外一半没有读完的情况。
//while(l < mid + 1 && a[l] <= a[r]) temp[k++] = a[l++];
//while(r < right + 1 && a[r] < a[l]) temp[k++] = a[r++];
while(l < mid + 1 && (a[l] <= a[r] || r == right + 1)) temp[k++] = a[l++];
while(r < right + 1 && (a[r] < a[l] || l == mid + 1)) temp[k++] = a[r++];
}
for(int i = 0; i < len; i++)
a[left + i] = temp[i];
// 归并排序
// 可以是稳定的
// 因为分层都要并n次,一共logn(2为底)次,所以时间复杂度是O(nlogn)。与初始序列无关。
// n较大时比较好。。。因为小了,也体现不出来优势。
// 空间复杂度O(n),因为每层归并都要一些临时数组,一层加起来就是n个
}
void insert_sort(int a[], int al)// 这样传过来的就是指针了。。。
{
// int al = sizeof(a) / sizeof(*a); //这里的a是一个指针(8 byte),所以结果和上面的不同。
for(int i = 1; i < al; i++)
{
int j = i;
// 下面这个操作,是为了找到第一个不大于j位置元素的值。
while(j && a[j] < a[j - 1])
{
int temp = a[j];
a[j] = a[j - 1];
a[j - 1] = temp;
j--;
}
}
// 插入排序
// 它是一个稳定的排序法(不会交换相同的数值的位置)
// 平均和最坏时间复杂度是O(n^2)
// 最好情况的复杂度是O(n),若开始就有序(这里默认是增序),那么只需要线性扫描一遍数组。复杂度为O(n)
// 适用于大部分已排好序的数组
// 空间复杂度O(1),在交换数据时,需要一个temp变量。
}
void printa(int a[], int al)
{
for(int i = 0; i < al; i++)
cout<<a[i]<<" ";
cout<<endl;
}
void swapa(int *a, int * b)
{
int temp = *a;
*a = *b;
*b = temp;
}
void select_sort(int a[], int al)
{
// 选择排序的思想是一个完全的暴力法
// cout<<"select sort"<<endl;
for(int i = 0; i < al - 1; i++)
{
int min = i;
// 从所有未排序的数值中找出最小值的位置
for(int j = i + 1; j < al; j++)
if(a[j] < a[min])
min = j;
// 交换(因为有交换操作的存在,使用这个算法变得不稳定,即相同的数值的相对位置在最终的排序结果里有可能发生变化)
int temp = a[i];
a[i] = a[min];
a[min] = temp;
}
// 选择排序
// 最好、最坏、平均的时间复杂度都是O(n^2)
// 空间复杂度是O(1)
// 不稳定
// n较小时使用
}
参考
http://www.sobugou.com/blog/2015/09/02/chang-jian-sortingsuan-fa-zong-jie/
https://segmentfault.com/a/1190000002595152
冒泡和插入比较
演示排序的网站
快排时间复杂度