### 引言
本节主要介绍基本的排序算法原理与实现 ,即:插入排序,选择排序,冒泡排序.
### 插入排序算法
#### 基本思想
插入排序首先考虑data中的前两个元素,即data[0]和data[1]。如果它们的次序颠倒了,就交换它们。然后,考虑第三个元素data[2],将其插入到合适的位置上。如果data[2]同时小于data[0]和data[1],那么data[0]和data[1]都要移动一个位置;data[0]放在位置1上,data[1]放在位置2上,而将data[2]放在位置0上。如果data[2]小于data[1],而不于data[0],那么只须把data[1]移动到位置2上,并由data[2]占据它的位置。最后,如果data[2]不小于前两个元素,它将保留在当前的位置。每一个元素data[i]都要插入到合适的位置j上,使0<=j<=i,所有比data[i]大的元素都要移动一个位置。
插入排序算法的要点如下:
~~~
insertionsort(data[],n){
for(i=1; i<n; i++)
move all elements data[j] greater than data[i] by one position;
place data[i] in its proper position;
}
~~~
注意,在每次迭代中,排序只限于数组的一小部分,并且只在最后一次迭代中才涉及到整个数组。如下图所示是当对数组[5 2 3 8 1]行insertionsort()方法时,数组所发生的一系变动。
![](https://box.kancloud.cn/7316b75b0009e5c4fefc2167844dd97c_653x236.jpg)
因数组中只有一个元素已排序,所以算法从第二元素位置即位置1开始排序。接着,对于每一个元素tmp = data[i],所有比tmp大的位素都要复制到下一位置上,而把tmp放在合适的位置上。
实现代码如下:
~~~
template< class T> void insertionsort(T data[], int n)
{
for(int i =1, j; i<n; i++){
T tmp = data[i];
for(j =i; i>0&&tmp< data[j-1]; j--)
data[j] = data[j-1];
data[j] =tmp;
}
}
~~~
插入排序的一个优点是只有需要时才对数组排序。如果数组已经排好序了,就不再对数组进行移动;只有变量tmp得到初始化,存储在变量中的值返回值原位置。可以识别出已排序的数组部分,并相应停止,但忽视元素可以已在合适位置上的事实。一个缺点是:如果插入数据项,所有比插入元素大的元素都必须移动。插入操作不是局部的,可能需要移动大量的元素。其中,在博文[【编程珠玑】插入排序](http://blog.csdn.net/songzitea/article/details/8761722)中,介绍了关于插入排序优化代码。
#### 算法分析
为了获取insertionsort()算法执行的移动和比较次数,首先要注意,外层的for语句执行n-1次。但是,比data[i]大的元素移动一个位置的次数并不总是相同的。
**最好的情况是**:数据已排好序。对于每个位置i,只需比较一匀,这样就只需进行n-1次比较(其数量级是o(n))和2(n-1)次移动,所有的移动和比较都是多余的。**最坏的情况是:**数据按相反顺序排序。在这种情况下,对于外层for的每次迭代i,都比较i次,即所有迭代的比较总数是
![](https://box.kancloud.cn/6846b2dea11d5b8ca124c188ca833807_302x44.jpg)
内层for中的赋值次数可以用相同的公式计算。把tmp在外层for中加载和卸载的次数加起来,得到总的移动次数:
![](https://box.kancloud.cn/d01bf077b156f3cba038715f5d6bfaca_274x42.jpg)
前面我们只考虑了极端情况。如果数据是随机排序的,结果如何呢?假设数据项占用数组单元的概率相等,在外层for的迭代i中,data[i]与其它元素的平均比较次数计算如下:
![](https://box.kancloud.cn/b665efd2261448b7aa32d4b411d841a1_197x46.jpg)
为了求得所有比较次数的平均值,对于所有的迭代i来说,都必须从1开始一直累加到n-1为止。结果是
![](https://box.kancloud.cn/53e52180010cbabcf4dd3fdd28927a25_363x55.jpg)
它的时间复杂度是O(n^2),大约是最坏情况下的比较次数的1/2。按照相同的方式,可以确定在外层for的迭代i中,data[i]需要移动0,1,..i-1次:即
![](https://box.kancloud.cn/99da55c289fcf2caef052871dee91235_223x54.jpg)
次,加上一定分会发生的两次移动。因此,在外层for中所有迭代中,平均移动次数为
![](https://box.kancloud.cn/2ceb8c135568c75d37860b4f6f952b6f_409x55.jpg)
其中时间复杂度为O(n^2)。
综上所述,对于随机顺序的数组来说,移动和比较的次数是与最好情况接近,还是与最坏情况接近?遗憾的是,它接近于后者,即:当数组的元素加倍时,平均需要付出4倍的努力来排序。
#### 测试输出结果
![](https://box.kancloud.cn/021db90f622f7db2b4be01a386a3d392_681x440.jpg)
### 选择排序算法
#### 基本思想
选择排序先找到时位置不合适的元素,再把它放在其最终的合适位置上,它试图仅在本地交换数组元素。基本思想是:先找出数组中的最小元素,将其与第一个位置上的元素进行交换。然后在剩余元素data[1],...,data[i-1]中找到最小元素,把它放到第二个位置上。这种选择和定位的方法是:在每次迭代中,找出元素data[i],....data[n-1]中的最小元素,然后将其与data[i]交换位置,直到所有的元素都放到了合适位置为止。
如下的伪代码反映出选择排序算法的简单性:
~~~
selectionsort(data[] ,n)
for i =0 到n-2
找出元素data[i]....data[n-1]中的最小元素;
将其与data[i]交换;
~~~
很明显,n-2是最后一个i值,因为寻如果除最后一个元素之外的所有元素都放在合适的位置上,那么第n个元素就是一定是最大的。下面提选择排序的C++代码:
~~~
template <class genType> void selectionsort (genType data[], int n)
{
int j,least;
for(int i =0; i<n-1; i++)
{
for(j = i+1,least = i; j< n; j++){
if(data[j] <data[least])
least =j;
}
genType temp = data[least];
data[least] = data[i];
data[i] = temp;
}
}
~~~
选择排序最大的优点是赋值次数少,这一点是其他算法无法比拟的。然而,所有情况下总是交换次数都为3(n-1)次,让人不太理想。
#### 算法分析
函数selectionsort( ) 的性能分析可通过两个for循环的上下界来简化,外层循环行了运行n-1次,对于任意一个0和n-2之间的整数i,内层循环迭代了j= (n-1)-1 次.因为为关键字的比较是在内层循环进行的。因此总共进行了O(n*n)次比较,这个数字对所有情况都是一样。只有交换次数是不同的。
#### 测试输出结果
![](https://box.kancloud.cn/5818a20959676518130be2b369add4a2_681x440.jpg)
### 冒泡排序算法
#### 基本思想
如果把要排序的数组设想成一个垂直的柱体,其中最小的元素在顶部,最大的元素在底部,冒泡排序算法就容易理解了。数组从底部往上扫描,如果相邻两个元素逆序,则交换两者。首先,比较数据项data[n-1]和data[n-2],如果逆序,则互序,接着比较data[n-2]和data[n-3],构建需要发生它们的顺序,一直到时data[1]和data[0].这样最小的元素就到了数组的顶部。然而,这还只是第一次遍历数组。再次对数组扫描,比较剩下的数据项,当需要时交换它们。但是这一次,最后比较的数据项是data[2]和data[1],因为最小的元素已经在合适的位置上,也就是位置0.第二次冒泡将第二个最小元素入在数组的第二个位置。即位置1.这一过程继续下,直到时最后一次,此时只比较一次,即比较data[n-1]与data[n-2],这时有可能要一次换。
算法的伪代码如下:
~~~
bubblesort(data[],n)
for (i =0; i<n-1; i++)
for(j =n-1; j>i;--j)
如果两者逆序,交换位置j和j-1的元素
~~~
下面是冒泡排序的实现:
~~~
template <class genType> void bubblesort( genType data[], int n)
{
for( int i =0; i<n-1; i++) {
for (int j =n-1; j>i;--j){
if(data[j]<data[j-1]){
genType temp = data[least];
data[least] = data[i];
data[i] = temp;
}
}
}
}
~~~
冒泡排序的主要缺点是元素需要要一步步地向上冒泡直到数组的顶部,这是非常花时间的。算法每次都考虑相邻两个元素寻,若为逆序,需要交换。如果元素需要从底部移到顶部,它和数组中的每一个元素交换位置。而不能像选择排序那样跳过它们。
#### 算法分析
算法中比较的次数在各种条件下都是相同的,都 等于内层for循环的总迭代次数O(n*n).最好情况是所有元素都已排好序,此时不需要交换。
#### 测试输出结果
![](https://box.kancloud.cn/a82491601d5b1550ed7713af9ddc9623_681x440.jpg)
### 比较三种算法性能
插入排序,选择排序,冒泡排序,这三种算法,当输入一个数组,运行100000次,所花费时间如图所下:
![](https://box.kancloud.cn/b4b9376e3aaa90a1454ddb3677414c66_681x440.jpg)
### 参考文献
[1] Adam Drozdek , Data Structures and Algorithms in C++,Third Edition.
[2] Thomas H.Cormnen ,Charles E.Lesersion et.al .Introduction to Algorithms second Edition.
转载请注明出处:[http://blog.csdn.net/ songzitea/article/details/8864084]
- 前言
- 螺旋矩阵、螺旋队列算法
- 程序算法艺术与实践:稀尔排序、冒泡排序和快速排序
- Josephu 问题:数组实现和链表实现
- 杨辉三角形算法
- 位图排序
- 堆排序的实现
- Juggling算法
- 【编程珠玑】排序与位向量
- 取样问题
- 变位词实现
- 随机顺序的随机整数
- 插入排序
- 二分搜索
- 产生不重复的随机数
- 约瑟夫环解法
- 快速排序
- 旋转交换或向量旋转
- 块变换(字符反转)
- 如何优化程序打印出小于100000的素数
- 基本的排序算法原理与实现
- 利用马尔可夫链生成随机文本
- 字典树,后缀树
- B-和B+树
- 程序算法艺术与实践引导
- 程序算法艺术与实践:基础知识之有关算法的基本概念
- 程序算法艺术与实践:经典排序算法之桶排序
- 程序算法艺术与实践:基础知识之函数的渐近的界
- 程序算法艺术与实践:递归策略之矩阵乘法问题
- 程序算法艺术与实践:递归策略之Fibonacci数列
- 程序算法艺术与实践:递归策略基本的思想
- 程序算法艺术与实践:经典排序算法之插入排序
- 程序算法艺术与实践:递归策略之递归,循环与迭代