数据结构之二分查找、简单排序(冒泡、插入、选择)、归并排序

1、二分查找只能应用于有序的数据,其实现也是比较简单的,每执行一次查找,则查找的范围变为原来的一半,其可查找的范围可以用如下函数表示:s = 2^r,其中s是查找的范围,而r表示步数。比如r =10,则2^10个数,查找某个数需要10步。其实现如下:

// 二分查找对应的元素的position
public int binarySearchFind(int searchKey) {
int lowPos = 0;
int upPos = items;
int curIn = 0;
while (true) {
curIn = (lowPos + upPos) / 2;
if (array[curIn] == searchKey) {
return curIn;
} else if (lowPos > upPos) {
return items;
} else {
if (array[curIn] > searchKey) {
upPos = curIn - 1;
} else {
lowPos = curIn + 1;
}}}}

2、冒泡排序

冒泡排序的思想:假设待排序的元素数量是N,则需要进行N-1趟冒泡排序,每一趟按照如下规则:

a)比较相邻的两个元素;

b)如果左边的元素比右边相邻的元素大,则交换,否则进行c;

c)向右移动一个位置,继续b操作;

d)当碰到已排序的元素<已排序的序列位于最右边>后,则返回至队列的最左端,进行下一趟的冒泡排序;

private void bubbleSort() {
if (nElems == 0 || nElems == 1) {// nElems:数组中元素的
return;
}
for (int i = nElems - 1; i > 0; i--) {// 冒泡的趟数<趟数也是每趟冒泡之后,最大元素的位置>
for (int pos = 0; pos < i; pos++) {// 每一趟中的相邻的两元素进行比较
swap(pos, pos + 1);
}}}

// 比较相邻的元素:如果当前的数值大于相邻的元素的数值大小,则交换
private void swap(int curPos, int afterPos) {
int temp = 0;
// 如果
if (array[curPos] > array[afterPos]) {
temp = array[curPos];
array[curPos] = array[afterPos];
array[afterPos] = temp;
}}

冒泡排序的效率:一般来说,数组有N个数据项,则第一趟排序中有N-1次比较,第二趟有N-2次比较,如此类推。这种序列的求和公式如下:

(N-1)+(N-2)+(N-3)+......+1 = N*(N-1)/2,比较和交换操作都和N^2成正比,也就是说冒泡排序运行需要O(N^2)时间级别。因此,这种排序算法的速度是很慢的。

3、选择排序

选择排序是改进版的冒泡排序,将必要的交换次数从O(N^2)减少到O(N)。但是比较次数仍然为O(N^2)。

与冒泡排序的最大区别是:减少了交换次数,在这个算法中有序的元素都排在左边,而冒泡排序中则是排列在右边。

算法思想:

假设待排序的元素为N个,则需要N-1趟选择排序

a)从最左边的第一个元素开始,寻找序列右边最小的元素,并将该元素放置在第一个位置;

b)从最左边第二个元素开始,寻找序列右边最小的元素,并将该元素放置在第二个位置;

c)依此类推,从最左边第X个(0<=X<=N-1)元素开始,寻找序列右边最小的元素,并将该元素放置在第X个位置;

// 选择排序
private void selectionSort() {
if (nElems == 0 || nElems == 1) {// nElems:数组中元素的
return;
}
for (int cur = 0; cur < nElems-1; cur++) {
int min = array[cur];
for (int searchPos = cur + 1; searchPos < nElems; searchPos++) {
if (min > array[searchPos]) {//减少了交换次数
swap(cur, searchPos);
}}}}

选择排序的效率:选择排序和冒泡排序执行了相同次数的比较:N*(N-1)/2。对于10个数据项,需要45次比较,但是,10个数据项只需少于10次交换。对于100个数据项,需要4950次比较,但只进行了不到100次交换。N 值很大时,比较的次数是主要的,所以结论是选择排序和冒泡排序一样运行了O(N^2)时间。但是,选择排序无疑更快,因为它进行的交换少得多。当N值较小时,特别是如果交换时间级比比较的时间级大多时,选择排序实际是相当快的。

4、插入排序

待排序的元素的左边序列都是有序的,右边是无序的。在左侧序列中,求得待排序元素的位置后,将该位置右侧的所有有序元素右移一个位置,以腾出一个位置插入待排序元素。如此反复。如下图所示:


实现:

// 插入排序
private void insertSort() {
if (nElems == 0 || nElems == 1) {// nElems:数组中元素的
return;
}
int in, out;// out:待排序的位置,in:待插入位置
for (out = 1; out < nElems; out++) {
int temp = array[out];
in = out;
while (in > 0 && array[in - 1] > temp) {// 依次向左比较,移动
array[in] = array[in - 1];
--in;
}
array[in] = temp;// in为最终待插入的位置
}
}

插入排序的效率:

在第一趟排序中,它最多比较一次,第二趟最多比较两次,依此类推。最后一趟最多,比较N-1次。因此有:1+2+3+...+N-1 = N*(N-1)/2;。然而,因为在每一趟排序发现插入点之前,平均只有一半真的进行了比较,因此我们除以2得到:N*(N-1)/4。

复制的次数大致等于比较的次数。然而,一次复制与一次交换的耗时是不同的,所以相对于随机数据。这个算法比冒泡快一倍,比选择排序略快。当然,对于基本有序的数据,插入排序几乎只需要O(N)的时间。

对于逆序排列的数据,每次比较和移动都会执行,所以插入排序不比冒泡排序快。

5、归并排序

       该排序(O(N*logN))比简单排序(O(N^2))的效率要高。 该排序有一个缺点就是它需要在存储器中有另外一个大小等于被排序的数据项数目的数组。如果初始数组几乎占满整个存储器,那么归并排序将不能工作。但是如果有足够的空间,归并排序是一个很好的选择。
思想:归并算法的中心是归并两个已经有序的数组。归并两个有序数组A和B,就生成了第三个数组C。数组C包含 数组A和B的所有数据项,并且使它们有序的排列在数组C中。

private int[] A = { 2, 9, 13, 47 };
private int[] B = { 7, 24, 39, 45, 67 };
private int[] C = new int[9];


// 归并排序
private void mergeSort() {
int index = 0, curLocA = 0, curLocB = 0;
while (curLocA < A.length && curLocB < B.length) {//两个数组均为到达尾部的情况
if (A[curLocA] <= B[curLocB])
C[index++] = A[curLocA++];
else {
C[index++] = B[curLocB++];
}
}
while (curLocB < B.length) {//B未到达尾部
C[index++] = B[curLocB++];
}
while (curLocA < A.length) {//A未到达尾部
C[index++] = A[curLocA++];
}


}

6、几种简单的排序算法(冒泡、选择、插入)比较

a)一般情况下几乎不太使用冒泡排序算法。当数据量较小的时候,它会有些应用价值。

b)选择排序虽然把交换次数降到了最低,但比较次数仍然很大。当数据量较小,并且交换数据比比较数据更耗时的情况下,可以应用选择排序。

c)在大多数情况下,假设当数据量比较小或基本上有序的时候,插入排序是这三种简单排序里面最好的选择。

对于更大数据量的排序来说,快速排序通常是更快的方法。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值