查找和排序
计算机的最重要的功能就是用来处理信息的,而涉及到对信息的处理,查找和排序则是其中最为重要的条目。这也难怪一代算法宗师Knuth在其7卷(拟)的巨著《The Art of Programming》中整整第三卷就是查找与排序了。
这里尽量以较为正规的代码风格的代码将这些经典的排序和查找算法描述出来,目的无他,温故而知新。
u 几个要用到的函数
1) 随机产生测试数据的函数
|
函数名称 |
InitData |
|||
|
作者 |
k_eckel |
时间 |
2005/07 |
|
|
函数功能 |
随机产生一个待排序的数组 |
|||
|
入口参数 |
Len |
产生数组的大小(长度) |
||
|
MAX_BOUND |
数组中最大元素的大小(默认为1000) |
|||
|
输出 |
数组的首地址 |
|||
|
//注意添加头文件:#include <ctime> template <class Item> Item* InitData(int len,int MAX_BOUND = 1000) { Item* data = new Item[len];
srand((unsigned)time(NULL));
for (int i = 0; i < len; i++) { data[i] = rand() % MAX_BOUND; }
return data; } |
||||
2) 打印数组内容
|
函数名称 |
PrintData |
||||
|
作者 |
k_eckel |
时间 |
2005/07 |
||
|
函数功能 |
打印数组,并可在打印前附加相关信息 |
||||
|
入口参数 |
data |
待输出数组 |
|||
|
Len |
数组的大小 |
||||
|
strInfo |
输出时显示的信息 |
||||
|
输出 |
无 |
||||
|
//注意添加头文件:#include <iostream> template <class Item> void PrintData(const Item* data,int len,const char* strInfo) { std::cout<<strInfo<<"........"<<endl;
for (int i = 0; i < len ; ++i) { std::cout<<data[i]<<" "; }
std::cout<<endl; } return data; |
|||||
3) 交换两个元素
|
template <class Item> void Exch2Item(Item& item1,Item& item2) { Item tmp = item1; item1 = item2; item2 = tmp; } |
以上3个函数在排序中经常要用到,这里给出,在相应的排序算法中不再重复。
插入排序
插入排序的思想很简单:假设前N-1个数据已经排好序了,第N个数据插到前面已经排好序的数组中。这和生活中一件事情有着异曲同工之妙:玩扑克牌的时候,很多人(包括我)都会按照花色从大小(或反之)进行排序,而每次再拿到新的牌,则插入到合适的位置,其实这就是插入排序的思想。
因此插入排序的过程可以分为两个步骤:1)找到新元素的插入位置;2)将将元素插入到1)中确定的位置,这就需要移动元素了。1)中确定位置我们可以采用简单的顺序遍历的方法得到,但是注意到由于前N-1个元素已经排好序,因此可以使用二分查找的方式实现。前者就是普通的插入排序,而后者就是二分插入排序。两者的实现源码为:
|
template <class Item> void InsertSort(Item* data,int len) { for (int i = 1; i < len; ++i) { Item tmp = data[i];
int j; //记录插入的位置
//找到插入的位置 for (j = 0; j < i; ++j) { if (data[i] < data[j]) break; }
for (int k = i; k > j; --k) { data[k] = data[k - 1]; }
data[j] = tmp; } } |
二分插入排序:
|
template <class Item> BInsertSort(Item* data,int len) { Item tmp; int idx; int l,r;
for (int i = 1; i < len; ++i) { tmp = data[i]; l = 0; r = i - 1; //二分查找获得插入位置 while (l <= r) { idx = (l + r)/2;
if (data[idx] < tmp) { l = idx + 1; } else { r = idx - 1; } }
for (int k = i; k > l; --k) { data[k] = data[k - 1]; }
data[l] = tmp; } } |
测试程序为:
|
int main(int argc,char* argv[]) { int* data = InitData<int>(10,100);
PrintData<int>(data,10,"before BubbleSort");
// InsertSort(data,10); // BInsertSort(data,10);
PrintData<int>(data,10,"after BubbleSort"); return 0; } |
查找与排序——冒泡排序、选择排序
冒泡排序和选择排序是排序算法中比较简单和容易实现的算法。冒泡排序的思想为:每一次排序过程,通过相邻元素的交换,将当前没有排好序中的最大(小)移到数组的最右(左)端。而选择排序的思想也很直观:每一次排序过程,我们获取当前没有排好序中的最大(小)的元素和数组最右(左)端的元素交换,循环这个过程即可实现对整个数组排序。两者的实现源码为:
冒泡排序:
|
template <class Item> void BubbleSort(Item* data,int len) { for (int i = 0;i < len; ++i) for (int j = len - 1; j > i; --j) { if (data[j] > data[j - 1]) { Exch2Item(data[j],data[j - 1]); } } } |
选择排序:
|
template <class Item> void SelectSort(Item* data,int len) { for (int i = 0; i < len; ++i) { int min = i; //找到当前未排序最小的元素 for (int j = i + 1; j < len; ++j) { if (data[j] < data[min]) min ++; }
Exch2Item(data[i],data[min]); } } |
测试程序为:
|
int main(int argc,char* argv[]) { int* data = InitData<int>(10,100);
PrintData<int>(data,10,"before BubbleSort");
// BubbleSort(data,10); SelectSort(data,10);
PrintData<int>(data,10,"after BubbleSort"); return 0; } |
需要说明和注意的是:InitData 和PrintData定义和实现在排序与查找——备用函数给出的,请参考上一篇文章。
0 0
查找与排序——堆排序
堆排序的过程就是不断建堆的过程:1)初始化得到初始堆;2)将堆顶元素和堆中最后元素交换,在构建堆。注意到这个交换过程除堆顶元素破坏堆,并不会影响堆(原因:堆顶元素或是最大,或是最小)。
因此堆排序有关键是两个建堆函数:自底向上堆化和自顶向下堆化,分别对应代码中FixUp()和FixDown()。需要注意的是:为了方便堆的操作,我们一般将N+1个元素的数组的第一个元素不用,使用1到N的元素建堆,原因是这样第i个元素的父节点严格对应i/2个元素,子节点严格对应2*i和2*i + 1元素(如果有的话)。当然你也可以改变这个约定,使用0到N-1作为建堆元素,这样的话就要注意元素和其父、子节点的关系(例如第i个元素的对应2*i + 1和2*i + 2元素(如果有的话)等)。
给出实现源码为:
|
//HeapSort.h
#ifndef _HEAP_SORT_H_ #define _HEAP_SORT_H_
#include <cstdlib>
#include <conio.h> #include <time.h>
#include <iostream> using namespace std;
template <class Item> void FixUp(Item* data,int idx) { while (idx > 1) { if (data[idx] < data[idx / 2]) ExchData(data[1],data[idx]);
idx = idx / 2; } }
template <class Item> void FixDown(Item* data,int len,int idx) { while (idx*2 < len) { int lchild = idx*2;
if ((lchild + 1) == len) { } else { if (data[lchild] < data[lchild + 1]) lchild ++; }
if (data[idx] < data[lchild]) { ExchData(data[idx],data[lchild]); }
idx = lchild; } }
template <class Item> void ExchData(Item& item1,Item& item2) { Item tmp = item1; item1 = item2; item2 = tmp; }
template <class Item> void HeapSort(Item* data,int len) { //construct the original heap for (int i = len / 2; i >= 1; i--) FixDown(data,len,i);
//begin to heap sort while (len > 1) { ExchData(data[1],data[len - 1]);
FixDown(data,--len,1); }
}
//data generator,the max Num is MAX_BOUND,1000 default. template <class Item> Item* InitData(int len,int MAX_BOUND = 1000) { Item* data = new Item[len];
srand((unsigned)time(NULL));
for (int i = 0; i < len; i++) { data[i] = rand() % MAX_BOUND; }
return data; }
//print the data template <class Item> void Print(Item* data,int len,char* info) { cout<<info<<endl;
for (int i = 1; i < len;i++) cout<<data[i]<<" ";
cout<<endl; }
#endif //~_HEAP_SORT_H_
|
测试程序为:
|
//main.cpp
#include "HeapSort.h"
#include <iostream> using namespace std;
const int DATA_LENGTH = 16;
int main(int argc,char* argv[]) { int* _data = InitData<int>(DATA_LENGTH);
Print(_data,DATA_LENGTH,"before heap sort.....");
HeapSort(_data,DATA_LENGTH);
Print(_data,DATA_LENGTH,"after heap sort......");
return 0; } |
0 0 0 (请您对文章做出评价)
查找与排序——快速排序
快速排序的主要思想就是:将待排序数组以某一个元素为阈值分为两个子列,一个子列包含所有比改阈值小的元素,另一个子列反之。这样只要将这两个子列排好序,整个数组也就排好序了。这里有一个关键的子过程就是划分的过程Partition,一般可以选择数组中任意的元素作为划分阈值,这里选择的是数组中最右端的元素。
Partition使用了二分查找类似的思想:使用两个索引器从数组的两端进行遍历,左边的索引器遇到比阈值大的元素停止,右边的索引器遇到比自己小的元素停止,然后交换这两个元素,依次循环。这样数组就划分为了比该阈值大和小(含等于)两个子列了。
两者的实现源码为:
|
template <class Item> int Partition(Item* data,int len) { Item tmp = data[len - 1];
int i = 0; int j = len - 1;
for (;;) { while (tmp > data[i]) i++;
while (tmp < data[--j]) { if (j == 1) break; }
if (i >= j) break;
Exch2Item(data[i],data[j]); } Exch2Item(data[i],data[len -1]); return i; } |
|
template <class Item> void QuickSort(Item* data,int len) { if (len <= 1) return ;
int i = Partition(data,len);
QuickSort(data,i); QuickSort(data + i,len - i); } |
测试程序为:
|
int main(int argc,char* argv[]) { using namespace sortAlg;
int* data = InitData<int>(10,100);
PrintData<int>(data,10,"before BubbleSort");
QuickSort(data,10);
PrintData<int>(data,10,"after BubbleSort"); return 0; } |
需要说明和注意的是:InitData 和PrintData定义和实现在排序与查找——备用函数给出的,请参考上一篇文章。
0 0 0 (请您对文章做出评价)
二分查找是提升系统的有效技术之一,特别是处理大规模的数据检索或者查询的时候,二分查找就更加表现出非凡的算法效率。并且二分查找的思想更加是值得我们好好体会和融会贯通的:
|
//查找 二分查找 template <class Item> int BinSearch(const Item* data,int len,Item sData) { int l = 0; int r = len - 1;
int idx = (l + r)/2;
while (data[idx] != sData) { if (data[idx] > sData) { r = idx - 1; } else if (data[idx] < sData) { l = idx + 1; }
if (l > r) { idx = -1; break; } else { idx = (l + r)/2; } }
return idx; } |
0 0 0 (请您对文章做出评价)
冒泡排序和选择排序是排序算法中比较简单和容易实现的算法。冒泡排序的思想为:每一次排序过程,通过相邻元素的交换,将当前没有排好序中的最大(小)移到数组的最右(左)端。而选择排序的思想也很直观:每一次排序过程,我们获取当前没有排好序中的最大(小)的元素和数组最右(左)端的元素交换,循环这个过程即可实现对整个数组排序。两者的实现源码为:
冒泡排序:
|
template <class Item> void BubbleSort(Item* data,int len) { for (int i = 0;i < len; ++i) for (int j = len - 1; j > i; --j) { if (data[j] > data[j - 1]) { Exch2Item(data[j],data[j - 1]); } } } |
选择排序:
|
template <class Item> void SelectSort(Item* data,int len) { for (int i = 0; i < len; ++i) { int min = i; //找到当前未排序最小的元素 for (int j = i + 1; j < len; ++j) { if (data[j] < data[min]) min ++; } Exch2Item(data[i],data[min]); } } |
测试程序为:
|
int main(int argc,char* argv[]) { int* data = InitData<int>(10,100); PrintData<int>(data,10,"before BubbleSort");
// BubbleSort(data,10); SelectSort(data,10);
PrintData<int>(data,10,"after BubbleSort"); return 0; } |
堆排序的过程就是不断建堆的过程:1)初始化得到初始堆;2)将堆顶元素和堆中最后元素交换,在构建堆。注意到这个交换过程除堆顶元素破坏堆,并不会影响堆(原因:堆顶元素或是最大,或是最小)。
因此堆排序有关键是两个建堆函数:自底向上堆化和自顶向下堆化,分别对应代码中FixUp()和FixDown()。需要注意的是:为了方便堆的操作,我们一般将N+1个元素的数组的第一个元素不用,使用1到N的元素建堆,原因是这样第i个元素的父节点严格对应i/2个元素,子节点严格对应2*i和2*i + 1元素(如果有的话)。当然你也可以改变这个约定,使用0到N-1作为建堆元素,这样的话就要注意元素和其父、子节点的关系(例如第i个元素的对应2*i + 1和2*i + 2元素(如果有的话)等)。
给出实现源码为:
|
//HeapSort.h #ifndef _HEAP_SORT_H_ #define _HEAP_SORT_H_ #include <cstdlib> #include <conio.h> #include <time.h> #include <iostream> using namespace std; template <class Item> void FixUp(Item* data,int idx) { while (idx > 1) { if (data[idx] < data[idx / 2]) ExchData(data[1],data[idx]); idx = idx / 2; } } template <class Item> void FixDown(Item* data,int len,int idx) { while (idx*2 < len) { int lchild = idx*2; if ((lchild + 1) == len) { } else { if (data[lchild] < data[lchild + 1]) lchild ++; } if (data[idx] < data[lchild]) { ExchData(data[idx],data[lchild]); } idx = lchild; } } template <class Item> void ExchData(Item& item1,Item& item2) { Item tmp = item1; item1 = item2; item2 = tmp; } template <class Item> void HeapSort(Item* data,int len) { //construct the original heap for (int i = len / 2; i >= 1; i--) FixDown(data,len,i); //begin to heap sort while (len > 1) { ExchData(data[1],data[len - 1]); FixDown(data,--len,1); }
} //data generator,the max Num is MAX_BOUND,1000 default. template <class Item> Item* InitData(int len,int MAX_BOUND = 1000) { Item* data = new Item[len];
srand((unsigned)time(NULL));
for (int i = 0; i < len; i++) { data[i] = rand() % MAX_BOUND; }
return data; } //print the data template <class Item> void Print(Item* data,int len,char* info) { cout<<info<<endl;
for (int i = 1; i < len;i++) cout<<data[i]<<" "; cout<<endl; } #endif //~_HEAP_SORT_H_ |
测试程序为:
|
//main.cpp #include "HeapSort.h" #include <iostream> using namespace std; const int DATA_LENGTH = 16; int main(int argc,char* argv[]) { int* _data = InitData<int>(DATA_LENGTH);
Print(_data,DATA_LENGTH,"before heap sort.....");
HeapSort(_data,DATA_LENGTH);
Print(_data,DATA_LENGTH,"after heap sort......");
return 0; } |
快速排序的主要思想就是:将待排序数组以某一个元素为阈值分为两个子列,一个子列包含所有比改阈值小的元素,另一个子列反之。这样只要将这两个子列排好序,整个数组也就排好序了。这里有一个关键的子过程就是划分的过程Partition,一般可以选择数组中任意的元素作为划分阈值,这里选择的是数组中最右端的元素。
Partition使用了二分查找类似的思想:使用两个索引器从数组的两端进行遍历,左边的索引器遇到比阈值大的元素停止,右边的索引器遇到比自己小的元素停止,然后交换这两个元素,依次循环。这样数组就划分为了比该阈值大和小(含等于)两个子列了。
两者的实现源码为:
|
template <class Item> int Partition(Item* data,int len) { Item tmp = data[len - 1];
int i = 0; int j = len - 1;
for (;;) { while (tmp > data[i]) i++;
while (tmp < data[--j]) { if (j == 1) break; }
if (i >= j) break;
Exch2Item(data[i],data[j]); } Exch2Item(data[i],data[len -1]); return i; } |
|
template <class Item> void QuickSort(Item* data,int len) { if (len <= 1) return ;
int i = Partition(data,len);
QuickSort(data,i); QuickSort(data + i,len - i); } |
测试程序为:
|
int main(int argc,char* argv[]) { using namespace sortAlg; int* data = InitData<int>(10,100); PrintData<int>(data,10,"before BubbleSort"); QuickSort(data,10); PrintData<int>(data,10,"after BubbleSort"); return 0; } |
二分查找是提升系统的有效技术之一,特别是处理大规模的数据检索或者查询的时候,二分查找就更加表现出非凡的算法效率。并且二分查找的思想更加是值得我们好好体会和融会贯通的:
|
//查找 二分查找 template <class Item> int BinSearch(const Item* data,int len,Item sData) { int l = 0; int r = len - 1; int idx = (l + r)/2; while (data[idx] != sData) { if (data[idx] > sData) { r = idx - 1; } else if (data[idx] < sData) { l = idx + 1; } if (l > r) { idx = -1; break; } else { idx = (l + r)/2; } } return idx; } |

浙公网安备 33010602011771号