冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。
它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素已经排序完成。
这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。
冒泡排序算法的原理如下:
-
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
-
对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
-
针对所有的元素重复以上的步骤,除了最后一个。
-
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
- 冒泡排序最好情况时间复杂度O(n),冒泡排序最坏情况下时间复杂度O(n^2) 冒泡排序空间复杂度O(1) 冒泡排序是一种稳定的排序算法
代码如下 :
//冒泡排序
//使用flag作为标记,当flag为1时,证明数组已将为有序数组,直接可以跳出循环
void BubbleSort(int *a, int len)
{
assert(a);
int flag = 0;
for (int i = 0; i < len - 1; i++){
flag = 1;
for (int j = 0; j < len - i - 1; j++){
//比较是否需要交换
if (a[j] > a[j+1]){
Swap(&a[j], &a[j + 1]);
flag = 0;
}
}
//如果数组已经有序则直接跳出循环
if (flag)
break;
}
}
快速排序(Quicksort)是对冒泡排序的一种改进。
快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据以一个枢纽div 分割成独立的两部分,其中左半部分的所有数据都比div要小,右半部分的所有数据都比div要大,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
递归实现 :
//快速排序
void QuickSort(int *a, int left, int right){
if (left >= right)
return;
assert(a);
//按照基准经left和right标记区间划分成两个部分
int div = PartionOne(a, left, right);
//排列左半部分
QuickSort(a, left, div - 1);
//排列右半部分
QuickSort(a, div + 1, right);
}
Partion()为进行一次库快速排序的算法, 常见的方法有 左右指针法 , 挖坑法 , 前后指针法
左右指针法
- 这里选取数组中最后一个数( 第一个数 )作为关键字key
- 设置两个变量begin和end分别数组的起始和结束下标
- begin从前往后, 当找到一个比key大的值时, right从后往前走, 当找到一个比key小的值时, 将其交换
- 继续重复上述步骤, 直到begin和end相遇, 再将key记录的数放到begin的位置, 并且返回begin
如上图所示 , 当begin>=end时, 一趟快速排序就完成了, 并在最后将key和begin的值进行交换,结 果就为 16 25 25 34 58 49
代码实现如下 :
//(左右指针法)寻找一个枢轴作为记录
int PartionOne(int *a, int begin, int end)
{
//选取序列的末尾作为关键字
int key = a[end];
//index用来记录关键字所在的下标
int index = end;
//保证key的左边都小于key,右边都大于key
while (begin < end){
//从左向右寻找比key大的数
if (begin < end && a[begin] <= key)
begin++;
//从右向左寻找比key小的数
if (begin < end && a[end] >= key)
end--;
//交换
Swap(&a[begin], &a[end]);
}
Swap(&a[begin], &a[index]);
return begin;
}
挖坑法
- 这里选取数组中最后一个数( 第一个数 )作为关键字key, 也为初始坑位
- 设置两个变量begin和end分别数组的起始和结束下标
- begin从前往后, 当找到一个比key大的值时, 将该数放到坑中, 这时的坑位就变作begin
- right从后往前走, 当找到一个比key小的值时, 将该数放到坑中, 这时的坑位就变作end
- 重复上述步骤直到begin和end相遇, 在将key放入到最后一个坑位
当begin>=end时, 将key放到最后一个坑中, 就完成了一次排序
代码如下 :
//挖坑法
int PartionTwo(int *a, int begin, int end)
{
//key为关键字,记录第一个坑里边的数据
int key = a[end];
while (begin < end){
if (begin < end && a[begin] <= key)
begin++;
a[end] = a[begin];
if (begin < end && a[end] >= key)
end--;
a[begin] = a[end];
}
a[begin] = key;
return begin;
}
前后指针法
- 定义一个变量next指向序列的起始位置, 定义一个变量prev指向next的前一个位置
- 当a[next]>key时, next不断向前走, 当a[next]<key时且prev和next相邻时, next和prev同时向前走
- 当a[next]<key且prev和next不相邻时, 将交换a[next]和a[prev]进行交换
代码如下 :
//前后指针法
int PrationThree(int *a, int begin, int end)
{
assert(a);
if (begin >= end)
return -1;
int key = a[end];
int prev = begin - 1;
int next = begin;
while (next < end){
//如果找到小于key的值,并且nxet和prev不相邻时进行交换。注意两个条件的先后位置不能更换
if (a[next] < key && ++prev != next)
Swap(&a[prev], &a[next]);
next++;
}
//注意:prev记得+1
Swap(&a[end], &a[++prev]);
return prev;
}
快速排序的优化
- 三数取中法 : 当所要排序的数组本身就是正序或者逆序时, 这样我们每次挑选的枢纽并没有起到划分的作用, 这是快速排序的效率会减低很多, 所以这里可以可以每次从起始位置, 中间位置和结束位置当中挑选出中间值, 将这个中间值和key交换, 这样就会使得每次划分接近于均等, 效率会提高不少
- 小区间法优化 (直接插入) : 当要排序的序列比较少时,为了避免开辟过多栈帧,使用插入排序来提高快速排序的效率
插入排序传送门 --- https://blog.csdn.net/ds19980228/article/details/82589860
代码如下 :
//三数取中法
int GetMid(int *a, int left, int right)
{
assert(a);
int mid = left + ((right - left) >> 2);
if (a[left] <= a[right]){
if (a[mid] < a[left])
return left;
else if (a[mid]>a[right])
return right;
else
return mid;
}
else{
if (a[mid] > a[left])
return left;
else if (a[mid] < a[right])
return right;
else
return mid;
}
}
//(左右指针法)寻找一个枢轴作为记录
int PartionOne(int *a, int begin, int end)
{
//mid用获取起始,中间和结束位置中的中间值
//选取序列的末尾作为关键字
int mid = GetMid(a, begin, end);
Swap(&a[mid], &a[end]);
int key = a[end];
int index = end;
//保证key的左边都小于key,右边都大于key
while (begin < end){
//从左向右寻找比key大的数
if (begin < end && a[begin] <= key)
begin++;
//从右向左寻找比key小的数
if (begin < end && a[end] >= key)
end--;
//交换
Swap(&a[begin], &a[end]);
}
Swap(&a[begin], &a[index]);
return begin;
}
//快速排序(小区间优化)
void QuickSortTwo(int *a, int left, int right){
if (left >= right)
return;
assert(a);
//当要排序的序列比较少时,为了避免开辟过多栈帧,使用插入排序
if (right - left <= 3){
for (int i = 0; i < right-1; i++){
int index = i + 1;
int cur = a[index];
if (a[i]>a[index]){
while (index > 0 && cur > a[index + 1]){
a[index + 1] = a[index];
++index;
}
}
a[index] = cur;
}
}
//按照基准经left和right标记区间划分成两个部分
int div = PrationOne(a, left, right);
//排列左半部分
QuickSort(a, left, div - 1);
//排列右半部分
QuickSort(a, div + 1, right);
}
快速排序 ( 非递归 )
在使用递归时, 主要就是为了划分子区间, 其本身也就相当于压栈的过程, 当我们要实现非递归时, 只需要使用一个栈将下标区间保存起来即可 栈传送门 --- https://blog.csdn.net/ds19980228/article/details/81741617
代码如下 :
//快速排序(非递归)使用栈来存放下标区间
void QuickSortRecNone(int *a, int left, int right){
if (left >= right)
return;
assert(a);
Stack s;
StackInit(&s);
StackPush(&s, left);
StackPush(&s, right);
while (StackEmpty(&s) != 0){
int end = StackTop(&s);
StackPop(&s);
int begin = StackTop(&s);
StackPop(&s);
int div = PartionOne(a, begin, end);
//将左右区间分别压栈
if (begin < div - 1){
StackPush(&s, begin);
StackPush(&s, div - 1);
}
if (end > div + 1){
StackPush(&s, div + 1);
StackPush(&s, end);
}
}
}
完整代码如下 :
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>
#include <string.h>
//栈的头文件
#include "Stack.h"
//交换数据
void Swap(int *m, int *n)
{
int tmp = *m;
*m = *n;
*n = tmp;
}
//三数取中法
int GetMid(int *a, int left, int right)
{
assert(a);
int mid = left + ((right - left) >> 2);
if (a[left] <= a[right]){
if (a[mid] < a[left])
return left;
else if (a[mid]>a[right])
return right;
else
return mid;
}
else{
if (a[mid] > a[left])
return left;
else if (a[mid] < a[right])
return right;
else
return mid;
}
}
//(左右指针法)寻找一个枢轴作为记录
int PartionOne(int *a, int begin, int end)
{
//选取序列的末尾作为关键字
int mid = GetMid(a, begin, end);
Swap(&a[mid], &a[end]);
int key = a[end];
int index = end;
//保证key的左边都小于key,右边都大于key
while (begin < end){
//从左向右寻找比key大的数
if (begin < end && a[begin] <= key)
begin++;
//从右向左寻找比key小的数
if (begin < end && a[end] >= key)
end--;
//交换
Swap(&a[begin], &a[end]);
}
Swap(&a[begin], &a[index]);
return begin;
}
//挖坑法
int PartionTwo(int *a, int begin, int end)
{
//key为关键字,记录第一个坑里边的数据
int key = a[end];
while (begin < end){
if (begin < end && a[begin] <= key)
begin++;
a[end] = a[begin];
if (begin < end && a[end] >= key)
end--;
a[begin] = a[end];
}
a[begin] = key;
return begin;
}
//前后指针法
int PrationThree(int *a, int begin, int end)
{
assert(a);
if (begin >= end)
return -1;
int key = a[end];
int prev = begin - 1;
int next = begin;
while (next < end){
//如果找到小于key的值,并且nxet和prev不相邻时进行交换。注意两个条件的先后位置不能更换
if (a[next] < key && ++prev != next)
Swap(&a[prev], &a[next]);
next++;
}
//注意:prev记得+1
Swap(&a[end], &a[++prev]);
return prev;
}
//快速排序
void QuickSort(int *a, int left, int right){
if (left >= right)
return;
assert(a);
//按照基准经left和right标记区间划分成两个部分
int div = PartionOne(a, left, right);
//int div = PartionTwo(a, left, right);
//int div = PrationThree(a, left, right);
//排列左半部分
QuickSort(a, left, div - 1);
//排列右半部分
QuickSort(a, div + 1, right);
}
//快速排序(小区间优化)
void QuickSortTwo(int *a, int left, int right){
if (left >= right)
return;
assert(a);
//当要排序的序列比较少时,为了避免开辟过多栈帧,使用插入排序
if (right - left <= 3){
for (int i = 0; i < right-1; i++){
int index = i + 1;
int cur = a[index];
if (a[i]>a[index]){
while (index > 0 && cur > a[index + 1]){
a[index + 1] = a[index];
++index;
}
}
a[index] = cur;
}
}
//按照基准经left和right标记区间划分成两个部分
//int div = PartionOne(a, left, right);
//int div = PartionTwo(a, left, right);
int div = PrationThree(a, left, right);
//排列左半部分
QuickSort(a, left, div - 1);
//排列右半部分
QuickSort(a, div + 1, right);
}
//快速排序(非递归)使用栈来存放下标区间
void QuickSortRecNone(int *a, int left, int right){
if (left >= right)
return;
assert(a);
Stack s;
StackInit(&s);
StackPush(&s, left);
StackPush(&s, right);
while (StackEmpty(&s) != 0){
int end = StackTop(&s);
StackPop(&s);
int begin = StackTop(&s);
StackPop(&s);
int div = PartionOne(a, begin, end);
//将左右区间分别压栈
if (begin < div - 1){
StackPush(&s, begin);
StackPush(&s, div - 1);
}
if (end > div + 1){
StackPush(&s, div + 1);
StackPush(&s, end);
}
}
}
//打印数组
void PrintArray(int *a, int len)
{
assert(a);
for (int i = 0; i < len; i++)
printf("%d ", a[i]);
printf("\n");
}
int main()
{
int a[] = { 58, 25, 49, 25, 16, 34 };
QuickSort(a, 0, sizeof(a) / sizeof(int)-1);
//QuickSortTwo(a, 0, sizeof(a) / sizeof(int)-1);
//QuickSortRecNone(a, 0, sizeof(a) / sizeof(int)-1)0;
PrintArray(a, sizeof(a) / sizeof(int));
system("pause");
return 0;
}