数据结构与算法--查找与排序(一)

Top

  1. 线性查找
  2. 二分查找
  3. 冒泡排序
  4. 插入排序
  5. 选择排序
  6. 快速排序
  7. 归并排序

1 线性查找

1.1 问题

线性查找,又称为顺序查找,是指在所有给定的值中从一端开始逐个检查每个元素是否为要查找的对象,直到找到为止的过程。

1.2 步骤

实现此案例需要按照如下步骤进行。

步骤一:逐个查找要查找的对象

代码如下:

  1. #include <stdio.h>
  2. typedef char DataType;
  3. int mySearch(DataType *ts, int n, const DataType d) {
  4. for (int i = 0; i < n; i++)
  5. if (ts[i] == d)
  6. return i;
  7. return -1;
  8. }

上述代码中,以下代码:

  1. int mySearch(DataType *ts, int n, const DataType d) {

是线性查找的函数头,它有三个形参,第一个形参是要查找的数组,第二个形参是数组中元素的个数,第三个形参是要查找的对象。

1.3 完整代码

本案例的完整代码如下所示:

  1. #include <stdio.h>
  2. typedef char DataType;
  3. int mySearch(DataType *ts, int n, const DataType d) {
  4. for (int i = 0; i < n; i++)
  5. if (ts[i] == d)
  6. return i;
  7. return -1;
  8. }
  9. int main()
  10. {
  11. char cs[6] = {‘*‘,‘A‘,‘B‘,‘C‘,‘D‘,‘E‘};
  12. printf("%d\n", mySearch(cs, 6, ‘*‘));
  13. printf("%d\n", mySearch(cs, 6, ‘A‘));
  14. printf("%d\n", mySearch(cs, 6, ‘D‘));
  15. printf("%d\n", mySearch(cs, 6, ‘C‘));
  16. }

2 二分查找

2.1 问题

二分查找算法,又称折半搜索、二分搜索,是一种在有序数组中查找某一特定元素的搜索算法。

二分查找算法的查找过程是这样的。首先,要求待查找的数组是排好序的数组,我们假设数组是升序的,即从小到大排序。然后,将要查找的元素与数组的中间元素相对比,如果相等,则表示要查找的元素被找到了,并停止查找;如果要查找的元素小于数组的中间元素,则从数组中间元素开始到数组最后的元素都大于要查找的元素,也就不需要在其中查找了,只需要在数组中间元素到数组第一个元素之间查找;如果要查找的元素大于数组的中间元素,则从数组中间元素到数组第一个元素都小于要查找的元素,也就不需要在其中查找了,只需要在数组中间元素到数组最后的元素之间查找。

由此可见,二分查找算法在查找的过程中只需对比一次,就可以使待查找的对象个数减少一半。查找速度非常快,所以二分查找算法得到了广泛的应用。

2.2 方案

为了理解二分查找算法,我们先假设有一个长度为11的数组,数组内容如图-1所示:

图-1

图-1中的数组是排好序的数组,从小到大排序。数组左边的数字7为数组的第一个元素,数组右边的数字94为数组的最后一个元素。要查找的元素假设为22。查找过程如下:

首先,确定查找范围,因为是第一次在数组中查找,所以整个数组都是待查找的范围,这样将带查找范围的下界low定为数组的第一个元素7,上界high定为数组的最后一个元素94,那么在上下界中间的元素就是数组元素56,我们用mid表示。

然后,将要查找的元素22与中间的元素,即数组元素56,相对比。因为22小于56,所以从中间元素mid到数组的上界high之间的所有元素都大于22,由图-1可以看出,mid到high之间分别是56、66、79、84、88、94,它们都大于22。由此可知这些元素都可以排除在下次查找的范围内了。即一次对比,就可以使待查找的对象个数减少一半。

最后,确定新的待查找范围,如图-2所示:

图-2

新的查找范围的下界low定为数组的第一个元素7,上界high定为数组的元素40,因为在上一次查找的过程中,数组元素56、66、79、84、88、94均已被排除在查找范围之外了。由上下界可以得到中间元素的数组元素为18,用mid表示。

然后,将要查找的元素22与中间的元素,即数组元素18,相对比。因为22大于18,所以从中间元素mid到数组的下界low之间的所有元素都小于22,由图-2可以看出,low到mid之间分别是7、12、18,它们都小于22。由此可知这些元素也都可以排除在下次查找的范围内了。

这样,再确定新的查找范围,如图-3所示:

图-3

新的查找范围的下界low定为数组元素22,上界high定为数组的元素40,因为在上一次查找的过程中,数组元素7、12、18均已被排除在查找范围之外了。由于上下界之间只有2个元素,所以中间元素的数组元素为22,用mid表示。

然后,将要查找的元素22与中间的元素,即数组元素22,相对比。因为相等,所以在数组中找到了要查找的元素。程序结束。

2.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:定义上下界

1)定义一个变量L,用于代表下界。

2)定义一个变量R,用于代表上界。

代码如下:

  1. int mySearch(DataType *ts, int n, const DataType d) {
  2. int L = 0;
  3. int R = n - 1;
  4. }

步骤二:确定中间点

1)定义一个变量M,用于代表中间点。

2)将中间点元素与要查找的元素对比,如果相等,则表示找到了要查找的元素,程序退出。

代码如下:

  1. int mySearch(DataType *ts, int n, const DataType d) {
  2. int L = 0;
  3. int R = n - 1;
  4. int M = (L + R)/2;
  5. if (ts[M] == d) return M;
  6. }

步骤三:重新确定查找范围

如果中间点元素小于要查找的元素,则将下界L更改为M+1,否则将上界R更改为M-1。

代码如下:

  1. int mySearch(DataType *ts, int n, const DataType d) {
  2. int L = 0;
  3. int R = n - 1;
  4. int M = (L + R)/2;
  5. if (ts[M] == d) return M;
  6. if (ts[M] < d)
  7. L = M+1;
  8. else
  9. R = M - 1;
  10. return -1;
  11. }

步骤四:按新范围重新查找

如果,一次未找到,则需要设置循环反复查找,直到找到或待查找范围内的元素个数为0。

代码如下:

  1. int mySearch(DataType *ts, int n, const DataType d) {
  2. int L = 0;
  3. int R = n - 1;
  4. while (L <= R) {
  5. int M = (L + R)/2;
  6. if (ts[M] == d) return M;
  7. if (ts[M] < d)
  8. L = M+1;
  9. else
  10. R = M - 1;
  11. }
  12. return -1;
  13. }

上述代码中,以下代码:

  1. while (L <= R) {

中的L<=R这个循环条件为真时,表示待查找范围内的数组元素个数至少为1。当这个循环条件为假时,则表示待查找范围内的数组元素个数已经为0,即没有可查找的对象了。

2.4 完整代码

本案例的完整代码如下所示:

  1. typedef char DataType;
  2. int mySearch(DataType *ts, int n, const DataType d) {
  3. int L = 0;
  4. int R = n - 1;
  5. while (L <= R) {
  6. int M = (L + R)/2;
  7. if (ts[M] == d) return M;
  8. if (ts[M] < d)
  9. L = M+1;
  10. else
  11. R = M - 1;
  12. }
  13. return -1;
  14. }
  15. int main()
  16. {
  17. char cs[6] = {‘*‘,‘A‘,‘B‘,‘C‘,‘D‘,‘E‘};
  18. printf("%d\n", mySearch(cs, 6, ‘*‘));
  19. printf("%d\n", mySearch(cs, 6, ‘A‘));
  20. printf("%d\n", mySearch(cs, 6, ‘D‘));
  21. printf("%d\n", mySearch(cs, 6, ‘C‘));
  22. }

3 冒泡排序

3.1 问题

冒泡排序是一种著名的排序方法。

冒泡排序的过程是这样的,首先,将待排序的数组中的第一个元素与第二个元素相对比,如果这两个元素的大小顺序不是我们要求的顺序,则将它们交换过来。然后,将待排序的数组中的第二个元素与第三个元素相对比,如果这两个元素的大小顺序也不是我们要求的顺序,则也将它们交换过来。下一步,是对比第三个元素与第四个元素,直至倒数第二个元素与最后一个元素相对比。这样一趟对比下来,小的数据元素会一点一点的往前放,而大的数据元素会一点一点的往后放。反复多趟的这样对比,直到所有数据都排好序为止。

3.2 方案

为了理解冒泡排序算法,我们先假设有一个长度为10的数组,数组内容如图-4所示:

图-4

图-4中的数组元素为无序状态,现需要将数组内的元素排序成从小到大的升序状态。可以先进行第一趟比较。首先比较第1个和第2个数,也就是数组第一个元素3和数组第二个元素2,将小数放前,大数放后。交换后如图-5所示:

图-5

然后,将图-5中的第2个和第3个数,也就是数组第二个元素3和数组第三个元素4,进行对比,由于3小于4,所以不进行交换。接着将图-5中的第3个和第4个数对比后发现也不需要交换。这样依次进行对比,直到图-5中的第7个和第8个数,也就是数组第七个元素9和数组第八个元素1,对比时,需要将它们交换过来,如图-6所示:

图-6

继续第一趟比较,图-6中的第8个和第9个数,也就是数组第八个元素9和数组第九个元素6,对比时,需要将它们交换过来,如图-7所示:

图-7

第一趟最后比较,图-7中的第9个和第10个数,也就是数组第九个元素9和数组第十个元素0,对比时,需要将它们交换过来,如图-8所示:

图-8

至此,第一趟比较结束,我们会发现数组中的最大值通过不断的位置交换,移到了数组最后一个元素的位置。但其它元素仍然是无序状态。所以需要进行第二趟比较,只是此次比较的范围应刨除数组的最后一个元素。当第二趟比较结束时,倒数第二大的数组元素值会被放置到数组的倒数第二个位置,如图-9所示:

图-9

第二趟比较结束后,依次进行后面的比较,直到数组内的元素按照升序排好为止。整个排序过程如图-10所示:

图-10

由图-10可以看出,实现冒泡排序算法时,需要使用嵌套的两个循环来实现:外层循环用于控制排序的趟次,里层循环用于控制每趟排序中的两两交换。

3.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:第一趟比较

设置循环,将待排序数组中的数组元素两两进行比较,如果有不符合要求的顺序的,进行交换。

代码如下所示:

  1. void bubble(DataType* a, int n) {
  2. for (int j = 0; j < n - i - 1; j++) {
  3. if (a[j] > a[j + 1]) {
  4. DataType t = a[j];
  5. a[j] = a[j + 1];
  6. a[j + 1] = t;
  7. flag = false;
  8. }
  9. }
  10. }

步骤二:多趟比较,直至排好序

设置外层循环,进行多趟比较。

代码如下所示:

  1. void bubble(DataType* a, int n) {
  2. for (int i = 0; i < n - 1; i++) {
  3. for (int j = 0; j < n - i - 1; j++) {
  4. if (a[j] > a[j + 1]) {
  5. DataType t = a[j];
  6. a[j] = a[j + 1];
  7. a[j + 1] = t;
  8. flag = false;
  9. }
  10. }
  11. }
  12. }

步骤三:设置标志,提高效率

代码如下所示:

  1. void bubble(DataType* a, int n) {
  2. for (int i = 0; i < n - 1; i++) {
  3. bool flag = true;
  4. for (int j = 0; j < n - i - 1; j++) {
  5. if (a[j] > a[j + 1]) {
  6. DataType t = a[j];
  7. a[j] = a[j + 1];
  8. a[j + 1] = t;
  9. flag = false;
  10. }
  11. }
  12. if (flag) break;
  13. }
  14. }

上述代码中,以下代码:

  1. bool flag = true;

定义一个布尔型变量,初始化为真,为真代表数组已经排好序了。

上述代码中,以下代码:

  1. for (int j = 0; j < n - i - 1; j++) {
  2. if (a[j] > a[j + 1]) {
  3. DataType t = a[j];
  4. a[j] = a[j + 1];
  5. a[j + 1] = t;
  6. flag = false;
  7. }
  8. }

是进行某趟比较,在该趟比较过程中,如果数组中所有元素两两比较均为假,则就不需要进行交换,而此时flag=false;语句也就不会执行。因此循环退出时,flag为真,即数组已经排好序了。

上述代码中,以下代码:

  1. if (flag) break;

表示如果flag为真,即数组已经排好序,则停止排序。

3.4 完整代码

本案例的完整代码如下所示:

  1. typedef int DataType;
  2. void bubble(DataType* a, int n) {
  3. for (int i = 0; i < n - 1; i++) {
  4. bool flag = true;
  5. for (int j = 0; j < n - i - 1; j++) {
  6. if (a[j] > a[j + 1]) {
  7. DataType t = a[j];
  8. a[j] = a[j + 1];
  9. a[j + 1] = t;
  10. flag = false;
  11. }
  12. }
  13. if (flag) break;
  14. }
  15. }
  16. void print(DataType* a, int n) {
  17. for (int i = 0; i < n; i++)
  18. printf("%d ", a[i]);
  19. printf("\n");
  20. }
  21. int main() {
  22. int a[10] = {3, 2, 4, 5, 7, 8, 9, 1, 6, 0};
  23. bubble(a, 10);
  24. print(a, 10);
  25. return 0;
  26. }

4 插入排序

插入排序算法是一种简单直观的排序算法。

插入排序的过程是这样的,首先,将待排序的数组分成两部分,一部分是已经排好序的部分,另一部分是未排好序的部分。在开始排序前,已排好序的部分只有一个数组元素,即数组的第一个元素;未排好序的部分是数组中除第一个元素外的其它所有元素。然后,将未排好序部分中的第一个数组元素,插入到已排序部分当中适当的位置,以保证已排序部分仍然是保持排序状态。此时,已排序部分就变成两个数组元素,而未排序部分的数组元素同时少了一个。依次类推,逐个从未排好序的部分拿出一个元素,插入到已排序部分当中适当的位置,直至数组按顺序排好为止。

4.1 方案

为了理解插入排序算法,我们先假设有一个长度为10的数组,数组内容如图-17所示:

图-11

图-17中的数组元素为无序状态,现需要将数组内的元素排序成从小到大的升序状态。首先将数组中的所有元素分成两个部分,一部分已排好序的部分,另一部分是未排好序的部分,在开始时,已排好序的部分只有一个数组元素,剩余的数组元素为未排好序的部分,如图-18所示:

图-12

将未排好序部分的一个元素取出,插入到已排好序部分的适当位置,如图-18中,将数组元素2从未排好序部分取出,与已排好序的部分的数组元素3对比,因为2小于3,所以将2插入到3的前面,如图-19所示:

图-13

此时,已排好序的部分变成了2个元素,而未排好序的部分少了一个元素,然后,再将未排好序部分的一个元素取出,插入到已排好序部分的适当位置,如图-19中,将数组元素4从未排好序部分取出,与已排好序的部分的数组元素3对比,因为4大于3,所以直接将4划入已排好序的部分,如图-20所示:

图-14

此时,已排好序的部分变成了3个元素,而未排好序的部分又少了一个元素,依次类推,逐个将未排好序的部分的元素插入到已排序的部分,直至整个数组都排好序为止。整个排序过程如图-21所示:

图-15

4.2 步骤

实现此案例需要按照如下步骤进行。

步骤一:将未排序部分的第一个元素插入到已排序部分

首先,定义临时变量,用于保存未排序部分的第一个元素

然后,设置循环,将未排序部分的第一个元素插入到已排序部分。

代码如下所示:

  1. void insert(DataType *a, int n) {
  2. int i = 1;
  3. //把选择的元素放在临时变量中
  4. DataType t = a[i];
  5. int j = 0;
  6. for (j = i; j > 0 && a[j - 1] > t; j--) {
  7. a[j] = a[j - 1];
  8. }
  9. a[j] = t;
  10. }

上述代码中,以下代码:

  1. for (j = i; j > 0 && a[j - 1] > t; j--) {
  2. a[j] = a[j - 1];
  3. }
  4. a[j] = t;

是将未排序部分的第一个元素t插入到已排序部分的适当位置a[j]。

步骤二:将所有未排序部分的元素插入到已排序部分

设置循环,逐个将未排序部分元素取出来,插入到已排序部分。

代码如下所示:

  1. void insert(DataType *a, int n) {
  2. for (int i = 1; i < n; i++) {
  3. //把选择的元素放在临时变量中
  4. DataType t = a[i];
  5. int j = 0;
  6. for (j = i; j > 0 && a[j - 1] > t; j--) {
  7. a[j] = a[j - 1];
  8. }
  9. a[j] = t;
  10. }
  11. }

4.3 完整代码

本案例的完整代码如下所示:

  1. typedef int DataType;
  2. void insert(DataType *a, int n) {
  3. for (int i = 1; i < n; i++) {
  4. //把选择的元素放在临时变量中
  5. DataType t = a[i];
  6. int j = 0;
  7. for (j = i; j > 0 && a[j - 1] > t; j--) {
  8. a[j] = a[j - 1];
  9. }
  10. a[j] = t;
  11. }
  12. }
  13. void print(DataType* a, int n) {
  14. for (int i = 0; i < n; i++)
  15. printf("%d ", a[i]);
  16. printf("\n");
  17. }
  18. int main() {
  19. int a[10] = {3, 2, 4, 5, 7, 8, 9, 1, 6, 0};
  20. insert(a, 10);
  21. print(a, 10);
  22. return 0;
  23. }

5 选择排序

5.1 问题

选择排序是一种简单直观的排序方法。

选择排序的过程是这样的,首先,在待排序的数组中找到一个最小的数组元素,将该最小数组元素与数组的第一个元素进行交换,这样交换之后,数组的第一个元素就变成了数组元素中的最小值。然后,再在待排序数组的剩余元素中找到一个最小的数组元素,将该最小数组元素与数组的第二个元素进行交换,这样交换之后,数组的第二个元素就变成了数组元素中的第二小的值。依次类推,直至数组按顺序排好为止。

5.2 方案

为了理解选择排序算法,我们先假设有一个长度为10的数组,数组内容如图-11所示:

图-16

图-11中的数组元素为无序状态,现需要将数组内的元素排序成从小到大的升序状态。首先在数组的所有元素中找到一个最小的元素,如图-12所示:

图-17

将该元素与数组的第一个元素进行交换,如图-13所示:

图-18

这样交换之后,数组的第一个元素就变成了数组元素中的最小值。然后,再在除第一个元素外的其它数组元素中,寻找最小的数组元素,如图-14所示:

图-19

将这个第二小的数组元素与数组的第二个位置的元素进行交换,如图-15所示:

图-20

这样交换之后,数组的第二个元素就变成了数组元素中的第二小的值。依次类推,再在剩余的数组元素中找最小值,与数组第三个位置的元素进行交换,直至数组排好序为止。整个排序过程如图-16所示:

图-21

由图-16可以看出,实现选择排序算法时,需要使用嵌套的两个循环来实现:外层循环用于控制排序的趟次,里层循环用于寻找指定范围内的最小值。

5.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:寻找数组中指定范围内的最小元素

首先,定义一个变量,用于存放最小元素在数组中的下标。

然后,设置循环,遍历数组找出最小元素。

代码如下所示:

  1. void selects(DataType *a, int n) {
  2. int i = 0;
  3. int k = i; //这里认为k就是最小元素的下标
  4. for (int j = i + 1; j < n; j++) {
  5. if (a[j] < a[k]) {
  6. k = j;
  7. }
  8. }
  9. }

步骤二:将最小元素与数组第一个位置的元素交换

代码如下所示:

  1. void selects(DataType *a, int n) {
  2. int i = 0;
  3. int k = i; //这里认为k就是最小
  4. for (int j = i + 1; j < n; j++) {
  5. if (a[j] < a[k]) {
  6. k = j;
  7. }
  8. }
  9. if (k != i) {
  10. swap(a[k], a[i]);
  11. }
  12. }

上述代码中,以下代码:

  1. if (k != i) {

是判断最小元素的下标是否就是数组第一个位置元素的下标,如果是则不需要再进行交换。

步骤三:将数组剩余元素排序

设置外层循环,依次寻找最小元素并进行交换。

代码如下所示:

  1. void selects(DataType *a, int n) {
  2. for (int i = 0; i < n - 1; i++) {
  3. int k = i; //这里认为k就是最小
  4. for (int j = i + 1; j < n; j++) {
  5. if (a[j] < a[k]) {
  6. k = j;
  7. }
  8. }
  9. if (k != i) {
  10. swap(a[k], a[i]);
  11. }
  12. }
  13. }

5.4 完整代码

本案例的完整代码如下所示:

  1. typedef int DataType;
  2. void selects(DataType *a, int n) {
  3. for (int i = 0; i < n - 1; i++) {
  4. int k = i; //这里认为k就是最小
  5. for (int j = i + 1; j < n; j++) {
  6. if (a[j] < a[k]) {
  7. k = j;
  8. }
  9. }
  10. if (k != i) {
  11. swap(a[k], a[i]);
  12. }
  13. }
  14. }
  15. void print(DataType* a, int n) {
  16. for (int i = 0; i < n; i++)
  17. printf("%d ", a[i]);
  18. printf("\n");
  19. }
  20. int main() {
  21. int a[10] = {3, 2, 4, 5, 7, 8, 9, 1, 6, 0};
  22. selects(a, 10);
  23. print(a, 10);
  24. return 0;
  25. }

6 快速排序

6.1 问题

快速排序算法是一种基于交换的排序,系统地交换反序的记录的偶对,直到不再有这样一来的偶对为止。它是对冒泡排序的一种改进。

快速排序的过程是这样的。首先,将待排序的数组从前向后和从后向前各取出一个元素进行对比交换,从而将待排序的数组分成两个部分,前一部分的所有元素都小于后一部分的所有元素,但前后两部分内部仍然是无序的状态。然后再将前一部分的所有元素从前向后和从后向前各取出一个元素进行对比交换,从而将前一部分的所有元素再分成两个部分,这两部分的前一部分的所有元素都小于后一部分的所有元素,依次类推,直到被分割的部分只有一个元素为止。下一步,再将后一部分的所有元素从前向后和从后向前各取出一个元素进行对比交换并分成两个部分。这样分到最后,数组将排好序。

6.2 方案

为了理解快速排序算法,我们先假设有一个长度为10的数组,数组内容如图-22所示:

图-22

图-22中的数组元素为无序状态,现需要将数组内的元素排序成从小到大的升序状态。首先将数组中的第一个元素3与最后一个元素0进行比较,因为3大于0,所以将它们两个进行交换,如图-23所示:

图-23

然后,将数组的第二个元素2与数组的最后一个元素3进行比较,因为2小于3,所以再那数组的第三个元素4与数组的最后一个元素3进行比较,因为4大于3,所以将它们两个进行交换,如图-24所示:

图-24

下一步,将数组的第三个元素3与数组的倒数第二个元素6进行比较,因为3小于6,所以再那数组的第三个元素3与数组的倒数第三个元素1进行比较,因为3大于1,所以将它们两个进行交换,如图-25所示:

图-25

下一步,将数组的第四个元素5与数组的倒数第三个元素3进行比较,因为5大于3,所以将它们两个进行交换,如图-26所示:

图-26

下一步,将数组的第四个元素3与数组的倒数第四个元素9进行比较,因为3小于9,所以再那数组的第四个元素3与数组的倒数第五个元素8进行比较,还因为3小于8,所以再那数组的第四个元素3与数组的倒数第六个元素7进行比较,再因为3小于7,不进行交换,至此将数组分成了两个部分,如图-27所示:

图-27

前一部分的所有元素均小于后一部分的所有元素。最后,先将前一部分按上述方法再分,再将后一部分再分,直到每一部分只有一个元素,无需排序为止。因为前一部分总是比后一部分小,所以当排序停止时,数组就已经排好序了。

6.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:终止分组条件

当只有一个元素时,停止排序。

代码如下:

  1. void qsorts(DataType *a, int n) {
  2. if(n <= 1) return;
  3. }

上述代码中,形参变量n得到的是数组元素个数 ,当n小于等于1时,使函数返回。

步骤二:将数组分成两部分

首先,定义一个变量L,用于存储数组的上界

然后,再定义一个变量R,用于存储数组的下界。

最后,设置循环将数组分成两部分

代码如下:

  1. void qsorts(DataType *a, int n) {
  2. if(n <= 1) return;
  3. int L = 0;
  4. int R = n - 1;
  5. while (L < R) {
  6. //一次分组
  7. while (L < R && a[L] <= a[R]) R--;
  8. DataType t = a[L];
  9. a[L] = a[R];
  10. a[R] = t;
  11. while (L < R && a[L] <= a[R]) L++;
  12. t = a[L];
  13. a[L] = a[R];
  14. a[R] = t;
  15. }
  16. }

上述代码中,以下代码:

  1. while (L < R && a[L] <= a[R]) R--;
  2. DataType t = a[L];
  3. a[L] = a[R];
  4. a[R] = t;

是从后向前寻找不符合顺序的元素,找到后,将其进行交换。

上述代码中,以下代码:

  1. while (L < R && a[L] <= a[R]) L++;
  2. t = a[L];
  3. a[L] = a[R];
  4. a[R] = t;

是从前向后寻找不符合顺序的元素,找到后,将其进行交换。

步骤三:递归分组

将分好的前后两部分用递归的方法再分。

代码如下:

  1. void qsorts(DataType *a, int n) {
  2. if(n <= 1) return;
  3. int L = 0;
  4. int R = n - 1;
  5. while (L < R) {
  6. //一次分组
  7. while (L < R && a[L] <= a[R]) R--;
  8. DataType t = a[L];
  9. a[L] = a[R];
  10. a[R] = t;
  11. while (L < R && a[L] <= a[R]) L++;
  12. t = a[L];
  13. a[L] = a[R];
  14. a[R] = t;
  15. }
  16. //继续左边分组
  17. qsorts(a, L);
  18. //继续右边分组
  19. qsorts(a + L + 1, n - L - 1);
  20. }

6.4 完整代码

本案例的完整代码如下所示:

  1. typedef int DataType;
  2. void qsorts(DataType *a, int n) {
  3. if(n <= 1) return;
  4. int L = 0;
  5. int R = n - 1;
  6. while (L < R) {
  7. //一次分组
  8. while (L < R && a[L] <= a[R]) R--;
  9. DataType t = a[L];
  10. a[L] = a[R];
  11. a[R] = t;
  12. while (L < R && a[L] <= a[R]) L++;
  13. t = a[L];
  14. a[L] = a[R];
  15. a[R] = t;
  16. }
  17. //继续左边分组
  18. qsorts(a, L);
  19. //继续右边分组
  20. qsorts(a + L + 1, n - L - 1);
  21. }
  22. void print(DataType* a, int n) {
  23. for (int i = 0; i < n; i++)
  24. printf("%d ", a[i]);
  25. printf("\n");
  26. }
  27. int main() {
  28. int a[10] = {3, 2, 4, 5, 7, 8, 9, 1, 6, 0};
  29. qsorts(a, 10);
  30. print(a, 10);
  31. return 0;
  32. }

7 归并排序

7.1 问题

归并排序是将两个已经排好序的序列合并成一个序列的操作。

归并排序的过程是这样的。首先,将待排序的数组中的元素从中间分为前后两部分。然后再将前一部分继续分成两部份,后一部分也继续分成两部分,依次类推,直到单个元素为止。最后两两按序合并,直到整个数组合并成一个有序数组为止。

7.2 方案

为了理解归并排序算法,我们先假设有一个长度为8的数组,数组内容如图-28所示:

图-28

现需要将数组内的元素先分成前后两部分,如图-29所示:

图-29

然后,将前后两部分继续分解,直至单个元素为止,如图-30所示:

图-30

下一步,重新两两合并,在合并的过程中注意先进行了排序,如图-31所示:

图-31

下一步,继续合并,直至整个数组排好序为止,如图-32所示:

图-32

7.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:将数组分成前后两部分

使用递归方法,将数组先分成前后两部分,然后再将前一部分分解,最后将后一部分分解。

代码如下:

  1. //内部使用递归
  2. void MergeSort(int sourceArr[],int startIndex,int endIndex)
  3. {
  4. int midIndex;
  5. if(startIndex<endIndex)
  6. {
  7. midIndex=(startIndex+endIndex)/2;
  8. MergeSort(sourceArr,startIndex,midIndex);
  9. MergeSort(sourceArr,midIndex+1,endIndex);
  10. }
  11. }

上述代码中,以下代码:

  1. midIndex=(startIndex+endIndex)/2;

是确定将数组分成前后两部分的中间点。

步骤二:将前后两部分重新按序合并

代码如下:

  1. #include<stdlib.h>
  2. #include<stdio.h>
  3. #define SIZE 8
  4. void Merge(int sourceArr[],int startIndex,int midIndex,int endIndex)
  5. {
  6. int start = startIndex;
  7. int i,j,k;
  8. int tempArr[SIZE];
  9. for(i=midIndex+1,j=startIndex;startIndex<=midIndex&&i<=endIndex;j++)
  10. if(sourceArr[startIndex]<=sourceArr[i])
  11. tempArr[j]=sourceArr[startIndex++];
  12. else
  13. tempArr[j]=sourceArr[i++];
  14. if(startIndex<=midIndex)
  15. for(k=0;k<=midIndex-startIndex;k++)
  16. tempArr[j+k]=sourceArr[startIndex+k];
  17. if(i<=endIndex)
  18. for(k=0;k<=endIndex-i;k++)
  19. tempArr[j+k]=sourceArr[i+k];
  20. for(i=start;i<=endIndex;i++)
  21. sourceArr[i]=tempArr[i];
  22. }
  23. //内部使用递归
  24. void MergeSort(int sourceArr[],int startIndex,int endIndex)
  25. {
  26. int midIndex;
  27. if(startIndex<endIndex)
  28. {
  29. midIndex=(startIndex+endIndex)/2;
  30. MergeSort(sourceArr,startIndex,midIndex);
  31. MergeSort(sourceArr,midIndex+1,endIndex);
  32. Merge(sourceArr,startIndex,midIndex,endIndex);
  33. }
  34. }

上述代码中,以下代码:

  1. int tempArr[SIZE];

是定义一个临时数组,用于合并过程中排序时使用。

上述代码中,以下代码:

  1. for(i=midIndex+1,j=startIndex;startIndex<=midIndex&&i<=endIndex;j++)
  2. if(sourceArr[startIndex]<=sourceArr[i])
  3. tempArr[j]=sourceArr[startIndex++];
  4. else
  5. tempArr[j]=sourceArr[i++];

是先挑选前后两部分中小的数组元素放入临时数组。

上述代码中,以下代码:

  1. if(startIndex<=midIndex)
  2. for(k=0;k<=midIndex-startIndex;k++)
  3. tempArr[j+k]=sourceArr[startIndex+k];

是将前一部分中剩余的元素放入临时数组。

上述代码中,以下代码:

  1. if(i<=endIndex)
  2. for(k=0;k<=endIndex-i;k++)
  3. tempArr[j+k]=sourceArr[i+k];

是将后一部分中剩余的元素放入临时数组。

上述代码中,以下代码:

  1. for(i=start;i<=endIndex;i++)
  2. sourceArr[i]=tempArr[i];

是将临时数组内的内容放回到待排序数组中。

7.4 完整代码

本案例的完整代码如下所示:

  1. #include<stdlib.h>
  2. #include<stdio.h>
  3. #define SIZE 8
  4. void Merge(int sourceArr[],int startIndex,int midIndex,int endIndex)
  5. {
  6. int start = startIndex;
  7. int i,j,k;
  8. int tempArr[SIZE];
  9. for(i=midIndex+1,j=startIndex;startIndex<=midIndex&&i<=endIndex;j++)
  10. if(sourceArr[startIndex]<=sourceArr[i])
  11. tempArr[j]=sourceArr[startIndex++];
  12. else
  13. tempArr[j]=sourceArr[i++];
  14. if(startIndex<=midIndex)
  15. for(k=0;k<=midIndex-startIndex;k++)
  16. tempArr[j+k]=sourceArr[startIndex+k];
  17. if(i<=endIndex)
  18. for(k=0;k<=endIndex-i;k++)
  19. tempArr[j+k]=sourceArr[i+k];
  20. for(i=start;i<=endIndex;i++)
  21. sourceArr[i]=tempArr[i];
  22. }
  23. //内部使用递归
  24. void MergeSort(int sourceArr[],int startIndex,int endIndex)
  25. {
  26. int midIndex;
  27. if(startIndex<endIndex)
  28. {
  29. midIndex=(startIndex+endIndex)/2;
  30. MergeSort(sourceArr,startIndex,midIndex);
  31. MergeSort(sourceArr,midIndex+1,endIndex);
  32. Merge(sourceArr,startIndex,midIndex,endIndex);
  33. }
  34. }
  35. //调用
  36. int main(int argc,char * argv[])
  37. {
  38. int a[SIZE] = {8,4,6,3,1,7,5,2};
  39. MergeSort(a,0,SIZE-1);
  40. for(int i=0;i<SIZE;i++)
  41. printf("%d ",a[i]);
  42. printf("\n");
  43. return 0;
  44. }
时间: 2024-10-10 21:27:56

数据结构与算法--查找与排序(一)的相关文章

【数据结构与算法】选择排序

选择排序没什么好说的,直接上代码吧 public class SelectSort { public void selectSort(int[] in) { int inLength = in.length; int minIndex = 0; for (int i = 0; i < inLength; i++) { minIndex = i; for (int j = i + 1; j < inLength; j++) { if (in[j] < in[minIndex]) { min

【数据结构与算法】希尔排序

希尔排序的时间复杂度是O(n^1.3)~O(n^2),空间复杂度是O(1). 代码如下: /** * 源码名称: ShellSort.java * 日期:2014-08-11 * 程序功能:希尔排序 * 版权:[email protected] * 作者:A2BGeek */ public class ShellSort { public void shellSort(int[] in) { int length = in.length; int span = length / 2; int i

数据结构与算法之——八大排序算法

附:关于这个主题,网上好的文章已经数不胜数,本篇是整合后的文章. 正文: 一.概述 排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 本文所指八大排序就是内部排序. 当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序.堆排序或归并排序序. 快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短: 二.排序算法详述 1.

数据结构与算法之--高级排序:shell排序和快速排序【未完待续】

高级排序比简单排序要快的多,简单排序的时间复杂度是O(N^2),希尔(shell)排序的是O(N*(logN)^2),而快速排序是O(N*logN). 说明:下面以int数组的从小到大排序为例. 希尔(shell)排序 希尔排序是基于插入排序的,首先回顾一下插入排序,假设插入是从左向右执行的,待插入元素的左边是有序的,且假如待插入元素比左边的都小,就需要挪动左边的所有元素,如下图所示: ==> 图1和图2:插入右边的temp柱需要outer标记位左边的五个柱子都向右挪动 如图3所示,相比插入排序

数据结构与算法之二 排序

假定,你要为你的生日聚会邀请你的朋友和亲戚.对此,你需要给他们打电话.你正在拥有10,000条记录的电话本中查找名为Steve的电话号码.然而,电话本中的记录是以随意顺序存储的.要在这样一个目录中查找你朋友的电话号码,你需要按顺序在目录中浏览每个条目.这将非常耗时,你如何解决此问题呢? 节省时间和高效搜索数据的简单解决方案是排序. 排序是按照某些预定义的顺序或序列排列数据的过程.此顺序可以是升序或降序. 如果数据被排序,则可以直接转到存储以'S'开头的姓名部分,因此减少了要遍历的记录数. 选择排

数据结构与算法-查找算法

第二章 查找和排序算法课时1:列表查找1.列表查找的含义:从对象中查找某一个特定的元素2.列表查找的方式包含两种:顺序查找和二分查找3.顺序查找算法:从开始一直搜索到最后一个元素进行查找,for循环,时间复杂度为O(n);4.二分查找针对有效的列表直接进行首尾二分查找,不断使得候选区减半,所以其时间复杂度为O(logn)4.二分查找只针对排序有序的列表查找有效高速,顺序查找针对任何列表:5.由于二分查找算法一般都需要进行排序,而排序算法的时间复杂度一般大于O(n),高于顺序查找:所以在内置的函数

数据结构与算法复习(一) 排序算法(I)

这篇文章将会介绍最常见的排序算法(使用 JavaScript 语言实现) PS:这里我会尽量使用语言无关的语法去写,大家不要太在意语言,重要的是算法的实现思路 1.冒泡排序 将数组分为有序区(左边)和无序区(右边) 每次从无序区的最后一个元素开始,一直向前冒泡到无序区的第一个位置,使其变成有序 function swap(A, i, j) { if (i === j) return [A[i], A[j]] = [A[j], A[i]] } function bubbleSort(A) { fo

2. C#数据结构与算法 -- 查找算法(顺序查找,哈希查找,二分查找(折半),索引,二叉)

1. 顺序查找算法 ===================================================== 算法思想简单描述: 最突出的查找类型就是从记录集的开始处顺次遍历每条记录,直到找到所要的记录或者是 到达数据集的末尾.这就是所谓的顺序查找.顺序查找(也被称为线性查找)是非常容易实现 的.从数组的起始处开始,把每个访问到的数组元素依次和所要查找的数值进行比较.如果找 到匹配的数据项,就结束查找操作.如果遍历到数组的末尾仍没有产生匹配,那么就说明此数 值不在数组内. ==

[数据结构与算法]二叉排序(搜索)树实现

声明:原创作品,转载时请注明文章来自SAP师太技术博客:www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将追究法律责任!原文链接:http://www.cnblogs.com/jiangzhengjun/p/4289804.html 定义 二叉排序树又称二叉查找树或二叉搜索树,它或者是一棵空树,或者是具有如下性质的二叉树:1.若它是左子树非空,则左子树上所有节点的值均小于根节点的值2.若它的右子树非空,则右子树上所有节点的值均大于根节点的值3.左.