一.排序
1.非线性排序
a.归并排序
1)基本思想:将待排序元素分成大小大致相同的2个子集合,分别对2个子集合进行排序,最终将排好序的子集合合并成为所要求的排好序的集合。
2)Step1: 把待排序的n个记录看作长度为1的有序序列。将相邻子序列两两归并为长度为2或1的有序序列;
Step2 :把得到的n/2个长度为2的有序子序列再归并为长度为 2*2 的有序序列;
Step3 :按Step2的方式,重复对相邻有序子序列进行归并操作,直到成为一个有序序列为止。
动画演示:http://ds.fzu.edu.cn/fine/resources/FlashContent.asp?id=93
3)代码
void merge_sort(int data[], int left, int right) { if(left< right) { int mid = (left+ right) / 2; merge_sort(data, left, mid); merge_sort(data, mid+1, right); merge(data, left, mid, right); } } void merge(int array[], int p, int q, int r) { int i,k; int begin1,end1,begin2,end2; int* temp = new int [r-p+1]; //申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列 //设定两个指针,最初位置分别为两个已经排序序列的起止位置 begin1= p; end1 = q; begin2 = q+1; end2 = r; k = 0; while((begin1 <= end1)&&( begin2 <= end2)) { if(array[begin1]<array[begin2]) {temp[k] = array[begin1]; begin1++; } else {temp[k] = array[begin2]; begin2++; } k++; } //若第一个序列有剩余,拷贝出来粘到合并序列尾 while(begin1<=end1) temp[k++] = array[begin1++]; //若第二个序列有剩余,拷贝出来粘到合并序列尾
while(begin2<=end2) temp[k++] = array[begin2++]; for (i = 0; i < (r - p +1); i++) //将排序好的序列拷贝回数组中 array[p+i] = temp[i]; delete[] (temp); }
4)复杂度
O(nlogn) 渐进意义下的最优算法
b.起泡排序
1)起泡排序基本思想: 设数组a中存放了n个数据元素,循环进行n-1趟如下的排序过程: 依次比较相临两个数据元素,若a[i]>a[i+1],则交换两个数据元 素,否则不交换。 当完成一趟交换以后,最大的元素将会出现在数组的最后一个位置。 依次重复以上过程,当第n-1趟结束时,整个n个数据元素集合中次 小的数据元素将被放置在a[1]中,a[0]中放置了最小的数据元素。
2)伪代码
1 // 将a中整数序列排列成自小至大有序的序列void bubblf_sort(array a[]) 2 { for(i=1,i<array.length;++i) //比较趟数 3 for(j=0;j<array.length-i;++j) 4 if(a[j]>a[j+1]) 5 a[j] ←→ a[j]; 6 }
c.快速排序
1)基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据 分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列(动画演示http://ds.fzu.edu.cn/fine/resources/FlashContent.asp? id=86)
2)算法过程:指定枢轴(通常为第一个记录) 通过一趟排序将以枢轴为中心,把待排记录分割为独立的两部分,使得左边记录的关键字小于枢轴值,右边记录 的关键字大于枢轴值 对左右两部分记录序列重复上述过程,依次类推,直到子序列中只剩下一个记录或不含记录为止。
3)算法步骤:
step1:分解(Divide):将输入的序列L[p..r]划分成两个非空子序列L[p..q]和L[q+1..r],使L[p..q]中任一元素的值不大于L[q+1..r]中任一元素的值。 step2:递归求解(Conquer):通过递归调用快速排序算法分别对L[p..q]和L[q+1..r]进行排序。
step3:合并(Merge):由于对分解出的两个子序列的排序是就地进行的,所以在L[p..q]和L[q+1..r]都排好序后不需要执行任何计算L[p..r]就已排好序。
4)代码
1 void quicksort(int number[], int left, int right) 2 { int q; 3 if(left < right) 4 { q=partition(number, left, right); //分解 5 quicksort(number, left, q-1); //排序 6 quicksort(number, q+1, right); 7 } 8 } 9 int partition(int number[], int left, int right) 10 { int s; 11 s = number[left]; 12 while(left<right) 13 { // 从表的两端交替地向中间扫描 14 while(number[right]>s) right--; 15 while(number[left]<s) left++; 16 SWAP(number[left], number[right]); 17 } 18 number[left]=s; // 枢轴记录到位 19 return left; // 返回枢轴位置 20 } 21 22 #define MAX 10 23 #define SWAP(x,y) {int t; t = x; x = y; y = t;} 24 void main() 25 { int number[MAX] = {0}; int i; 26 srand(time(NULL)); 27 cout<<"排序前:"; 28 for(i = 0; i < MAX; i++) 29 { number[i] = rand() % 100; 30 cout<<setw(6)<<number[i]; 31 } 32 quicksort(number, 0, MAX-1); 33 cout<<" 排序后:"; 34 for(i = 0; i < MAX; i++) 35 cout<<setw(6)<<number[i]; 36 cout<<" "; 37 }
5) 复杂度
对于n个成员,快速排序法的比较次数大约为n*logn 次,交换次数大约为(n*logn)/6次。如果n为100,冒泡法需要进行4950 次比较,而快速排序 法仅需要200 次。快速排序法的性能与中间值的选定关系密切,如果每一次选择的中间值都是最大值(或最小值),该算法的速度就会大大下降。 快速 排序算法最坏情况下的时间复杂度为O(n2),而平均时间复杂度为O(n*logn)
2.线性排序
a.计数排序
1)解释:
集合A有n个元素,每一个元素的值是介于0到k之间的整数。
输入:A[1..n] 存放排序结果:B[1..n] 临时存储: C[0..k](存储数组每个坐标做对应的数值的个数)
计数排序的基本思想是对每一个输入元素x,确定出小于x的元素个数。有了这一信息,就可以把x直接放到它在最终输出数组 的位置上。例如:若有5个元素小于x,则x就属于第6个输 出位置 。
2)代码:
Count-Sort(A,B,k) //0<=a[i]<=k for i←0 to k do C[i] ←0 for j← 1 to length[A] do C[A[j]]← C[A[j]] +1 for i← 1 to k do C[i]← C[i] +C[i-1] for j←length[A] downto 1 do B[C[A[j]]] ←A[j] C[A[j]]← C[A[j]] -1
b.基数排序
1)背景
基数排序是指用多关键字的“最低位优先”方法排序,即对待排序的记录序列按照关键字从低位到高位的顺序交替地进行“分组”,“收集”,最终得到有序的记录序列。将一次“分组”,“收集”称为一趟。对于由 d位关键字组成的复合关键字,需要经过d趟的“分配”与“收集”。在基数排序的“分配”与“收集”操作过程中,为了避免数据元素的大量移动,通常采用链式存储结构存储待排序的记录序列 。
适用于:要求对n个数的序列L={a1, a2 , …,an}进行排序,其中每个数恰好由k位数字组成,每位数字均取自{0,1,…,9}。
2)过程
基数排序的“分配”与“收集”过程 第一趟 :
基数排序的“分配”与“收集”过程 第二趟 :
基数排序的“分配”与“收集”过程 第三趟 :
3)特点
基数排序适用于待排序的记录数目较多,但其关键字位数较少,且关键字每一位的基数相同的情况.若待排序记录的关键字有d位就需要进行d次"分配"与"收集",即共执行d趟,因此,若d值较大,基数排序的时间效率就会随之降低.基数排序是一种稳定的排序方法.
c.桶排序
1)背景
桶排序的思想是把[0,1)划分为n个大小相同的子区间,每一子区间是一个桶。然后将n个记录分配到各个桶中。因为关键字序列是均匀分布在[0,1)上的,所以一般不会有很多个记录落入同一个桶中。由于同一桶中的记录其关键字不尽相同,所以必须采用关键字比较的排序方法(通常用插入排序)对各个桶进行排序,然后依次将各非空桶中的记录连接(收集)起来即可。
2)适用于
这种排序思想基于以下假设:假设输入的n个关键字序列是随机分布在区间[0,1)之上。若关键字序列的取值范围不是该区间,只要其取值均非负,我们总能将所有关键字除以某一合适的数,将关键字映射到该区间上。但要保证映射后的关键字是均匀分布在[0,1)上的。
3)过程
4)代码
Bucket-Sort(A) { n=length[A] for(i=1;i<=n;i++) //分配过程. 将A[i]插入到桶B[「n(A[i].key)」]中; for(i=0;i<n;i++) //排序过程当B[i]非空时用插人排序将B[i]中的记录排序; for(i=0;i<n;i++) //收集过程若B[i]非空,则将B[i]中的记录依次输出到R中; }
各种算法总结
二.查找
方法:二分查找
a.背景-给定已按升序排好序的n个元素a[1:n],现要在这n个元素中找出一特定元素x。
b.分析-适用分治法求解问题的基本特征:
该问题的规模缩小到一定的程度就可以容易地解决;
该问题可以分解为若干个规模较小的相同问题;
分解出的子问题的解可以合并为原问题的解;
分解出的各个子问题是相互独立的。
很显然此问题分解出的子问题相互独立,即在a[i]的前面或后面查找x是独立的子问题,因此满足分治法的第四个适用条件
c.二分检索原理
将二分检索问题问题表示为:I=(n, a1, … , an, x) 选取一个下标k,可得到三个子问题: I1=(k-1, a1, … , ak-1, x) I2=(1, ak , x) I3=(n-k, ak+1, … , an, x)
d.问题描述
已知一个按非降次序排列的元素表a1,a2,…,an,判定某个给定元素x是否在该表中出现,若是,则找出该元素在表中的位置,并置于j,否则,置j为0。
一般解决办法:从小标0到最后依次查找,成功和不成功最坏情况下需要O(n)。
e.二分搜索算法代码
1 public static int binarySearch(int [] a, int x, int n) 2 { 3 // 在 a[0] <= a[1] <= ... <= a[n-1] 中搜索 x 4 // 找到x时返回其在数组中的位置,否则返回-1 5 int left = 0; int right = n - 1; 6 while (left <= right) 7 { 8 int mid = (left + right)/2; // mid = low + ((high - low) / 2); 9 if (x == a[mid]) return mid; 10 if (x > a[mid]) left = mid + 1; 11 else right = mid - 1; 12 } 13 return -1; // 未找到x 14 }
二分搜索递归代码
1 //初始 left = 0; right = N - 1; 2 public static int binarySearch(int a[], int x, int left ,int right) 3 { 4 // 找到x时返回其在数组中的位置,否则返回-1 5 if (left>right) return -1;// 未找到x 6 else 7 { 8 int mid = (left + right)/2; 9 if (x == a[mid]) return mid; 10 if (x > a[mid]) return binarySearch(a, x, mid+1,right); 11 else return binarySearch( a,x,left,mid-1); 12 } 13 }
f.二分搜索的时间复杂度
若n在区域[2k-1, 2k)中,则对于一次成功的检索,二分检索算法至多作k次比较。