2018-3-20
我花了18元从App Store里面买了Algorithms
,这里面的步骤还是比较容易理解的。
1.冒泡排序
冒泡排序是两两相邻的进行比较,平均时间复杂度是O(n^2),是比较稳定的排序,因为两个相等的数是不会进行交换的。
#include<iostream>
using namespace std;
const int N = 10;
int x[N+1]={33,25,4,23,65,31,57,8,6,32};
void bubbleSort(){
for (int i=0;i<N-1;i++){
for (int j=0;j<N-i-1;j++){
if (x[j]>x[j+1]){
int t=x[j];
x[j]=x[j+1];
x[j+1]=t;
}
}
}
}
void disPlay(){
for (int i=0;i<N-1;i++){
cout<<x[i]<<" ";
}
cout<<x[N-1]<<endl;
}
int main(){
bubbleSort();
disPlay();
return 0;
}
当然,如果说我们原来的数组就是有序的话,那么我们还需要进行那么多次比较,岂不是很浪费时间,所以就有了优化过后的冒泡排序。
#include<iostream>
using namespace std;
const int N = 10;
int x[N+1]={33,25,4,23,65,31,57,8,6,32};
bool flag;
void bubbleSort(){
for (int i=0;i<N-1;i++){
flag=false;
for (int j=0;j<N-i-1;j++){
if (x[j]>x[j+1]){
// int t=x[j];
// x[j]=x[j+1];
// x[j+1]=t;
x[j]=x[j]^x[j+1];
x[j+1]=x[j]^x[j+1];
x[j]=x[j]^x[j+1];
flag=true;
}
}
if (!flag) break;
}
}
void disPlay(){
for (int i=0;i<N-1;i++){
cout<<x[i]<<" ";
}
cout<<x[N-1]<<endl;
}
int main(){
bubbleSort();
disPlay();
return 0;
}
其实也就是发现每一个相邻两个的顺序都ok的话,我们的排序也就结束了。
我们还可以不使用第三个变量来使两个数进行交换,我们只要使用异或运算即可,一个数和它本身异或的结果为0。
2.快速排序
我们发现冒泡排序需要多次交换,而快排只有在需要的时候才会进行交换。
以一个数为基准(通常是第一个数),把比它大的数放在它的后面,把比它小的数放在它的前面,对前面和后面两组数实施相同的操作。平均复杂度为O(nlogn),是不稳定的排序。
快排每一次都会有一个数在它正确的位置上。
#include<iostream>
using namespace std;
const int N = 10;
int x[N+1]={33,25,4,23,65,31,57,8,6,32};
void quickSort(int low, int high){
if (low>=high){
return ;
}
int key=x[low],i=low,j=high;
while (i<j){
while (i<j&&x[j]>=key) j--;
x[i]=x[j];
while (i<j&&x[i]<=key) i++;
x[j]=x[i];
}
x[i]=key;
quickSort(low,i-1);
quickSort(i+1,high);
}
void disPlay(){
for (int i=0;i<N-1;i++){
cout<<x[i]<<" ";
}
cout<<x[N-1]<<endl;
}
int main(){
quickSort(0,N-1);
disPlay();
return 0;
}
我们先将第一个数x[low]保存起来为key,先从后往前找,找到第一个小于key的数的下标j,将x[j]赋值给x[low],然后再从前往后找,找到第一个比key大的数的下标i,把x[i]赋值给原来找到的x[j],重复这个过程直到大于key的都在它的后面,小于key的都在它的前面,然后把key保存在”中间的位置”,这是一个递归的过程,我们需要对前面和后面的数做相同的处理。
又或者你可以用另一种方法,每次得到大于key的x[i]与小于key的x[j]进行交换,然后最后再将x[low]的值与x[i]进行交换,需要我们注意的是,我们第一个while循环得是对j进行的操作。
这里踩了异或运算的一个坑,两个相同的数异或的结果是零,那么此时它们两个是不能用异或运算进行交换的…
#include<iostream>
using namespace std;
const int N = 10;
int x[N+1]={33,25,4,23,65,31,57,8,6,32};
void quickSort(int low, int high){
if (low>=high){
return ;
}
int key=x[low],i=low,j=high;
while (i<j){
while (i<j&&x[j]>=key) j--;
while (i<j&&x[i]<=key) i++;
if (i<j){
x[i]=x[i]^x[j];
x[j]=x[i]^x[j];
x[i]=x[i]^x[j];
}
}
int t=x[i];
x[i]=x[low];
x[low]=t;
quickSort(low,i-1);
quickSort(i+1,high);
}
void disPlay(){
for (int i=0;i<N-1;i++){
cout<<x[i]<<" ";
}
cout<<x[N-1]<<endl;
}
int main(){
quickSort(0,N-1);
disPlay();
return 0;
}
3.插入排序
每次将前面的数看作是有序的,那么问题就变成了向一个有序的序列里插入数字使它们依然保持有序,默认第一个数就是有序的。平均时间复杂度为O(n^2),是一个稳定的排序。
#include<iostream>
using namespace std;
const int N = 10;
int x[N+1]={33,25,4,23,65,31,57,8,6,32};
void insertSort(){
int i=1;
while (i<N){
if (x[i]>=x[i-1]) {
i++;
continue;
}
int key=x[i],j=i-1;
while (x[j]>key&&j>=0){
x[j+1]=x[j];
j--;
}
if (j!=i-1) x[j+1]=key;
//防止自我插入
i++;
}
}
void disPlay(){
for (int i=0;i<N-1;i++){
cout<<x[i]<<" ";
}
cout<<x[N-1]<<endl;
}
int main(){
insertSort();
disPlay();
return 0;
}
其实还是比较简单的,先把当前要插入的数记住为key,然后从后往前进行比较,如果大于key的话,就后移一位,否则的话就把这个数插入进去。
4.选择排序
从所有记录中选出最小的一个数据元素与第一个位置的记录交换;然后在剩下的记录当中再找最小的与第二个位置的记录交换,循环到只剩下最后一个数据元素为止。平均时间复杂度为O(n^2),是一个不稳定的排序方法。
#include<iostream>
using namespace std;
const int N = 10;
int x[N+1]={33,25,4,23,65,31,57,8,6,32};
void selectSort(){
for (int i=0;i<N;i++){
int k=i;
for (int j=i+1;j<N;j++){
if (x[j]<x[k]) k=j;
}
if (k!=i){
int t=x[k];
x[k]=x[i];
x[i]=t;
}
}
}
void disPlay(){
for (int i=0;i<N-1;i++){
cout<<x[i]<<" ";
}
cout<<x[N-1]<<endl;
}
int main(){
selectSort();
disPlay();
return 0;
}
注意这里的选择排序是一个不稳定的排序,如果说我们的序列为:5,7,3,5,2,1,那么我们在得到第一个最小的值的时候就要与第一个5进行交换,此时它们俩的相对顺序就不一样了,所以说是不稳定的排序。
5.希尔排序
这是一种有增量的插入排序,插入排序是以1作为增量,而希尔排序是不断将增量缩小为1而实现的插入排序,平均时间复杂度为O(n^3/2),是一种不稳定的排序。
#include<iostream>
using namespace std;
const int N = 10;
int x[N+1]={33,25,4,23,65,31,57,8,6,32};
void shellSort(){
int i,j,increMent=N/3+1;
for (increMent=N/3+1;increMent>=1;increMent=increMent/3+1){
for (i=increMent+1;i<N;i++){
if (x[i]>=x[i-increMent]) continue;
int key=x[i];
for (j=i-increMent;j>=0&&x[j]>key;j-=increMent){
x[j+increMent]=x[j];
}
if (j+increMent!=i) x[j+increMent]=key;
}
if (increMent==1) break;
}
}
void disPlay(){
for (int i=0;i<N-1;i++){
cout<<x[i]<<" ";
}
cout<<x[N-1]<<endl;
}
int main(){
shellSort();
disPlay();
return 0;
}
和插入排序的方法差不多,唯一的区别就是说这里进行插入排序的数字之间是有间隔的,而这个间隔其实是会影响我们的性能的。
如果说满足某个条件就退出for循环的话就把他写在for(;XXXX
;)这里,因为它的意思就是在不满足这个条件时就结束循环。
6.归并排序
一种分治的思想。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。平均时间复杂度为O(nlogn),是一种稳定的排序方法。
#include<iostream>
using namespace std;
const int N = 10;
int x[N+1]={33,25,4,23,65,31,57,8,6,32},y[N+1];
void mergeSort(int low, int high){
if (low>=high){
return ;
}
int mid=(low+high)/2;
mergeSort(low,mid);
mergeSort(mid+1,high);
int i=low,j=mid+1,k=low;
while (i<=mid&&j<=high){
if (x[i]<=x[j]) y[k++]=x[i++];
else y[k++]=x[j++];
}
while (i<=mid) y[k++]=x[i++];
while (j<=high) y[k++]=x[j++];
for (int i=low;i<=high;i++) x[i]=y[i];
}
void disPlay(){
for (int i=0;i<N-1;i++){
cout<<x[i]<<" ";
}
cout<<x[N-1]<<endl;
}
int main(){
mergeSort(0,N-1);
disPlay();
return 0;
}
7.堆排序
每次都取堆顶的元素,将其放在序列最后面,然后将剩余的元素重新调整为最大堆,依次类推,最终得到排序的序列。它是一种不稳定的排序。
#include<iostream>
using namespace std;
const int N = 10;
int x[N+1]={33,25,4,23,65,31,57,8,6,32},y[N+1];
void heapAdjust(int s,int e){
int temp=x[s],j;
for (j=2*s;j<=e;j*=2){
if (j<e&&x[j]<x[j+1]) ++j;
if (temp>=x[j]) break;
x[s]=x[j];
s=j;
}
x[s]=temp;
}
void heapSort(){
for (int i=N/2-1;i>=0;i--){
//叶子节点被认为是一个合法的堆
heapAdjust(i,N-1);
}
for (int i=N-1;i>0;i--){
int t=x[0];
x[0]=x[i];
x[i]=t;
heapAdjust(0,i-1);
}
}
void disPlay(){
for (int i=0;i<N-1;i++){
cout<<x[i]<<" ";
}
cout<<x[N-1]<<endl;
}
int main(){
heapSort();
disPlay();
return 0;
}