一.冒泡排序法
重复地走访要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。
算法描述和分析
1、比较相邻的元素,如果前一个比后一个大,就把它们两个调换位置。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。
2、针对所有的元素重复以上的步骤,除了最后一个。
3、持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
最差时间复杂度 | O(n^2) |
---|---|
最优时间复杂度 | O(n) |
平均时间复杂度 | O(n^2) |
最差空间复杂度 | 总共O(n),需要辅助空间O(1) |
交换函数
void myswap(int *a,int i,int j)
{
int tmp = a[i];
a[i]= a[j];
a[j] = tmp;
}
冒泡排序
void mysort(int *a,int len)
{
int i,j;
for(i=0;i<len-1;i++)//限制将最大数沉底的次数
{
for(j=0;j<len-i-1;j++)//执行将最大数沉底的操作
{
if(a[j]>a[j+1])
{
myswap(a,j,j+1);
}
}
}
}
递归方式实现
void mysort1(int *a,int len)
{
int i,j;
if(len == 1)//退出条件
return;
for(j=0;j<len-1;j++)//执行一次将最大数沉底
{
if(a[j]>a[j+1])
{
myswap(a,j,j+1);
}
}
mysort1(a,len-1);
}
二.冒泡排序法改进——鸡尾酒排序
鸡尾酒排序等于是冒泡排序的轻微变形。不同的地方在于从低到高然后从高到低,而冒泡排序则仅从低到高去比较序列里的每个元素。
算法描述和分析
1、从左往右走,将大的数沉底
2、从右往左走,将小的数上浮
3、持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
最差时间复杂度 | O(n^2) |
---|---|
最优时间复杂度 | O(n) |
平均时间复杂度 | O(n^2) |
鸡尾酒排序
void mysort(int *a,int len)
{
int left = 0;
int right = len-1;
int i;
while(left<right)
{
for(i=left;i<right;i++)//从左往右走,大的数沉底
{
if(a[i]>a[i+1])
{
myswap(a,i,i+1);
}
}
right--;
for(i=right;i>left;i--)//从右往左走,小的数上浮
{
if(a[i]<a[i-1])
{
myswap(a,i,i-1);
}
}
left++;
}
}
递归方式实现
void mysort1(int *a,int left,int right)
{
int i;
if(left >= right)
return;
for(i=left;i<right;i++)//从左往右走,大的数沉底
{
if(a[i]>a[i+1])
{
myswap(a,i,i+1);
}
}
right--;
for(i=right;i>left;i--)//从右往左走,小的数上浮
{
if(a[i]<a[i-1])
{
myswap(a,i,i-1);
}
}
left++;
mysort1(a,left,right);
}
三.选择排序
首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾。
算法描述和分析
1、先找出最小元素的下标,将最小元素放在起始位置
2、利用一个for循环限制寻找的次数
最差时间复杂度 | О(n²) |
---|---|
最优时间复杂度 | О(n²) |
平均时间复杂度 | О(n²) |
最差空间复杂度 | О(n) total, O(1) |
选择排序
void mysort(int *a,int len)
{
int i,j;
for(i=0;i<len-1;i++)//限制找的次数
{
int min = i;
for(j=i+1;j<len;j++)//找出最小元素的下标
{
if(a[min]>a[j])
{
min = j;
}
}
if(min != i)//如果在前面的数刚好就是最小元素,则不需要进行交换
{
myswap(a,min,i);//将最小元素放在前面,例如第一次的时候将找到的最小元素与a[0]进行交换
}
}
}
四.插入排序
通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
注意:插入排序不适合对于数据量比较大的排序应用。
算法描述和分析
1、从第一个元素开始,该元素可以认为已经被排序
2、取出下一个元素,在已经排序的元素序列中从后往前扫描
3、如果该元素(已排序)大于新元素,将该元素移到下一位置
4、重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
5、将新元素插入到该位置后
6、重复步骤2~5
插入排序:在一个已经排好序的数组里面插入一个元素
void mysort(int *a,int len)
{
int i;
for(i = 0;i<len;i++)
{
int fget = a[i];
int j = i-1;
while(j >= 0 && a[j] > fget)
{
a[j+1] = a[j];
j--;
}
a[j+1] = fget;
}
}
五.二分插入排序
与直接排序算法最大的区别在于查找插入位置时使用的是二分查找的方式,在速度上有一定提升。
算法描述和分析
1、从第一个元素开始,该元素可以认为已经被排序
2、取出下一个元素,在已经排序的元素序列中二分查找到第一个比它大的数的位置
3、将新元素插入到该位置后
4、重复上述两步
二分插入排序
void mysort(int *a,int len)
{
int i,j;
for(i = 0;i<len;i++)
{
int fget = a[i];
int left = 0;
int right = i-1;
while(left <= right)
{
int mid = (right + left)/2;
if(a[mid] > fget)//就是将小的值放在中间下标的左边,大的值放在中间下标的右边
right = mid - 1;//当中间元素的值大于左边的值时,右边界为中间下标-1
else
left = mid + 1;//当中间元素的值小于左边的值时,左边界为中间下标+1
}
for(j = i-1;j>=left;j--)//此时注意j可以为0
{
a[j+1] = a[j];
}
a[left] = fget;//注意这里,左边界对应的值就是我们 需要的值
}
}
六.希尔排序
排序过程:先取一个正整数d1<n,把所有序号相隔d1的数组元素放一组,组内进行直接插入排序;然后取d2<d1,重复上述分组和排序操作;直至di=1,即所有记录放进一个组中排序为止。
算法描述和分析
1、 先取一个小于数组长度len的整数d1作为第一个增量,把文件的全部记录分成d1个组。
2、所有距离为d1的倍数的记录放在同一个组中,在各组内进行直接插入排序。
3、取第二个增量d2<d1重复上述的分组和排序,
4、直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
希尔排序
void mysort(int *a,int len)
{
int gap = 1;//差值
while(gap < len)
{
gap = gap *3 +1;
}
while(gap > 0)
{
int i;
for(i = gap;i < len;i++)//注意此时的i++用得比较巧妙,意思就是并不需要一组一组的进行排序操作,可以一起进行操作
{
int fget = a[i];
int j = i-gap;
while(j >= 0 && a[j] > fget)
{
a[j + gap] = a[j];
j -= gap;
}
a[j + gap] = fget;
}
printf("gap = %d\n",gap);
myprint(a,len);
gap = gap/3;
}
}
七.堆排序
堆排序是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构(通常堆是通过一维数组来实现的),并同时满足堆的性质:即子结点的键值总是小于(或者大于)它的父节点。
算法描述和分析
1、建堆,从第一个非叶结点开始,并对堆进行调整,调整为大顶堆
2、调整为大顶堆的步骤,找出左右顶结点的最大元素下标,调用交换函数,使得根结点存放的是最大元素,注意使用递归
3、排序:将堆顶元素和当前堆最后一个元素进行交换,调整第一个结点成为大顶堆
最差时间复杂度 | O(n log n) |
---|---|
最优时间复杂度 | O(n log n) |
平均时间复杂度 | O(n log n) |
最差空间复杂度 | O(n) |
调整堆:使其成为大顶堆
void herify(int *a,int index,int len)
{
int lindex = 2*index +1;//左孩子结点的下标:下标从0开始
int rindex = 2*index +2;//右孩子结点的下标
int max = index;
if(lindex < len && a[lindex]>a[max])//找出左右根结点中最大元素的下标
max = lindex;
if(rindex < len && a[rindex]>a[max])
max = rindex;
if(max != index)
{
myswap(a,max,index);
herify(a,max,len);//递归,在最大元素的位置继续继续交换
}
}
堆排序
void mysort(int *a,int len)
{
int i;
for(i = len/2-1;i>0;i--)//建堆:从第一个非叶结点开始 2n+1 = len-1===>n= len/2-1
{
herify(a,i,len);
}
int count =len-1;//调整的次数
for(i = len-1;i>0;i--)//排序
{
myswap(a,0,i);//堆顶元素和当前堆最后一个元素进行交换
herify(a,0,count);//调整第一个结点成为大顶堆
//herify(a,0,i);//此时也可以写成这样,但是为了好理解,用上述表示方法
count--;
}
}
八.归并排序
归并排序是建立在归并操作上的一种有效的排序算法。
算法描述和分析
1、对左边数组进行归并排序:以递归方式
2、对右边数组进行归并排序
3、合并左右两个数组
合并
void merge(int *a,int left,int mid,int right, int *tmp)
{
int i = left;//左边
int j = mid+1;//右边
int k = 0;//临时
while(i <= mid && j<= right)//复习顺序表的内容
{
if(a[i] <= a[j])
tmp[k++] = a[i++];
else
tmp[k++] = a[j++];
}
while(i <= mid)
{
tmp[k++] = a[i++];
}
while(j<= right)
{
tmp[k++] = a[j++];
}
for(i=0;i<k;i++)
{
a[left + i] = tmp[i];
}
}
归并排序
void mysort(int *a,int left,int right, int *tmp)
{
if(left <right)
{
int mid = (left + right)/2;
mysort(a,left,mid,tmp); //左边归并排序
mysort(a,mid+1,right,tmp); //右边归并排序
merge(a,left,mid,right,tmp); //合并
}
}
九.快速排序
通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序。
算法描述和分析
1、算基准值下标
2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
3、递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。
求基值下标
int partion(int *a, int left, int right)
{
int pivot = a[right]; //以最后一个元素作为基准值
while(left < right)
{
while (a[left] <= pivot && left < right)
{
left++;
}
if (left < right)
{
a[right] = a[left];
right--;
}
while (a[right] >= pivot && left < right)
{
right--;
}
if (left < right)
{
a[left] = a[right];
left++;
}
}
a[left] = pivot;
return left;
}
void quickSort(int *a, int left, int right)
{
if (left < right)
{
int pivotIndex = partion(a, left, right); // 算基准值下标
quickSort(a, left, pivotIndex-1); // 对左半部分做快速排序
quickSort(a, pivotIndex+1, right); // 对右半部分做快速排序
}
}