您的位置:首页 > 编程语言 > C语言/C++

c++实现冒泡排序和快速排序

2017-05-09 21:42 183 查看
上一篇博客中对插入排序进行了分析介绍,接下这里对冒泡排序和快速排序进行深入的分析介绍。冒泡排序和快速排序有个统称叫作“交换排序”,叫作交换排序的原因很容易理解,因为冒泡排序和快速排序都是通过多次交换两个数的位置从而达到排序的效果。接下来让我们好好理解下如何通过交换两个数的位置最终达到排序的效果。

注:介绍算法时仍然使用上一节用到的数据int a[10] = { 45, 12, 36, 76, 45, 9, 33, 19, 87, 23 };

交换排序算法的主要思想就是交换两个数据的位置。

1、冒泡排序

名如其意,冒泡排序的过程就像我们生活中看到的水中冒泡的现象一样,通过不断的交换将最大数据交换到顶端,然后再将次大的数据交换到倒数第二位,...,这样一个过程像是起泡一样。经过一轮冒泡排序将数组的最大的数据移到数组的最右端,然后再将次大数移到数组的倒数第二个位置....

从上述分析可知,给定n个数的数组,要经过 n-1 趟冒泡排序,其中每一趟排序都是从左到右进行将最大数据交换到右边去。

1.1 代码实现

#include<iostream>
using namespace std;
void BubbleSort(int *a, int n)
{
for (int i = 0; i < n - 1; ++i)
{
for (int j = 0; j < n - i - 1; ++j)
{
if (a[j] > a[j + 1])
{
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
}
int main()
{
int a[10] = { 45, 12, 36, 76, 45, 9, 33, 19, 87, 23 };
cout << "排序前:";
for (int i = 0; i < 10; ++i)
{
cout << a[i] << " ";
}
cout << endl;
BubbleSort(a, 10);
cout << "排序后:";
for (int i = 0; i < 10; ++i)
{
cout << a[i] << " ";
}
cout << endl;
return 0;
}


1.2 排序结果





从上面的图可以看出,每一趟排序将一个大的数据从左到右交换到右边。并且算法是稳定的。

1.3 性能分析

因为冒泡排序需要经过 n-1 趟排序,每一趟排序都要从左端开始往右将每两个相邻元素进行比较,总的时间复杂度为O(n^2)。

1.4 冒泡排序的改进

从上面例子可以看出当进行完第6趟排序后已经达到最终的排序效果,后面的第7,8,9趟排序没有交换元素。因此针对这一点我们可以对冒泡排序进行改进,从一定程度上能够起到加速的效果,改进思想如下:

如果在某一趟排序中没有交换数据的操作,数据已经有序,我们可以提前终止。

在代码的实现上很简单,在每一趟排序开始的时候只用加一个bool变量用以标价该趟排序过程中是否有元素的交换即可。这样可以达到提前终止的效果,从而减少后面不必要的比较。

改进后的代码

#include<iostream>
using namespace std;
void BubbleSort(int *a, int n)
{
for (int i = 0; i < n - 1; ++i)
{
bool f = false;
for (int j = 0; j < n - i - 1; ++j)
{
if (a[j] > a[j + 1])
{
f = true;
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
if (!f) break;
}
}
int main()
{
int a[10] = { 45, 12, 36, 76, 45, 9, 33, 19, 87, 23 };
cout << "排序前:";
for (int i = 0; i < 10; ++i)
{
cout << a[i] << " ";
}
cout << endl;
BubbleSort(a, 10);
cout << "排序后:";
for (int i = 0; i < 10; ++i)
{
cout << a[i] << " ";
}
cout << endl;
return 0;
}
改进后的效果



改进后我们可以提前终止,只进行了7趟排序,就往成了任务。

2、快速排序

个人感觉快速排序就是一个“挖坑填数”的思想。何谓挖坑填数呢,就是在数组中挖掉一个数,然后将另一个满足条件的数据填到这个坑里。下面我们通过例子说明这样一个过程。

2.1 基本步骤

快速排序可以分为三个步骤:

(1)选择基准元素;

(2)以基准元素为准将数据分割为左右两部分,基准元素左边的数据要比基准元素小,基准元素右边的数据比基准元素大;

(3)然后对基准元素左边、右边分别使用快速排序。(递归的一个过程)

下面我们分别针对这三个步骤分别做详细地说明:

对于步骤(1),通常情况下我们会以数组最左端的数据作为基准元素。后面我们分析快速排序算法的性能时,再对基准元素的选择做详细分析。

对于步骤(2),我们需要设两个指针分别指向数组的两端,使用一个临时变量存放基准元素(相当于挖了一个坑)。然后右端指针开始向左移动,找到第一个比基准元素小的数据并将该数据填到坑中(相当于填了一坑又挖了一个坑),这是右端指针所指的地方出现了一个坑。然后向右动左侧指针,找到第一个比基准元素大的数据,并将其填到坑中(填了一个坑又又挖了一个坑)。然后向左移动右侧指针....,这样当左边指针和右边指针指向同一位置时将临时保存的数据填到这个位置上就完成了第一趟快速排序,此时基准元素左边的小于基准元素,右边的大于基准元素。

对于步骤(3),是一个递归的过程,分别对左边右边使用快速排序。
2.2 实现代码

#include<iostream>
using namespace std;

//选择一个基准元素,进行一次划分
int partition(int start, int end, int *a)
{
int temp = a[start];
while (start < end)
{
while (start < end && a[end] >= temp)
{
--end;
}
a[start] = a[end];
while (start < end && a[start] <= temp)
{
++start;
}
a[end] = a[start];
}
a[start] = temp;
return start;
}

void QuickSort(int start, int end, int *a)
{
if (start < end)
{
int middle = partition(start, end, a);
QuickSort(start, middle - 1, a);//对左侧递归调用
QuickSort(middle + 1, end, a);//对右侧递归调用
}
}

void print(int *a, int n)
{
for (int i = 0; i < n; ++i)
{
cout << a[i] << " ";
}
cout << endl;
}
int main()
{
int a[10] = { 45, 12, 36, 76, 45, 9, 33, 19, 87, 23 };
print(a, 10);
QuickSort(0, 9, a);
print(a, 10);
return 0;
}
2.3 排序结果






具体的排序过程如下:

第一趟快速排序:



第二趟快速排序:



第三趟快速排序:



第四趟快速排序:



第五趟快速排序:



经过5趟快速排序完成数据排序。通过例子可以观察到第3趟排序后,数据已经达到最后的排序效果,因此我们可以采取某些策略使得快速排序提前停止,进而调高性能。

2.4 性能分析

快速排序的平均时间复杂度为O(n*logn)。但是当待排序数据已经基本有序时,快速排序退化成冒泡排序,此时时间复杂度为O(n^2)。

2.5 快速排序改进

快速排序可以改进的地方:

(1)基准元素的选取;

选择基准元素时,通常使用“三者取其中”的方法。也就是说根据给定的start和end,我们通常使用中间位置的数据最为基准元素。

(2)在一次划分过程中,可以提前结束

在指针start加1和end减1的过程中都进行“起泡操作”,即每移动一次指针我们就比较两个相邻数据是否“逆序”,如果逆序就交换他们的位置。另外设一个bool变量用于标记是否发生了“起泡”的交换操作,如果没有发生说明不许要对这一端再进行排序,从而达到提前结束的效果。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息