第一篇:C++ 八种排序算法总结及实现
八种排序算法总结之C++版本
五种简单排序算法
一、冒泡排序
【稳定的】
void BubbleSort(int* a,int Count)//实现从小到大的最终结果 { int temp;for(int i=1;i for(int j=Count-1;j>=i;j--) if(a[j] < a[j-1]) { temp = a[j]; a[j] = a[j-1]; a[j-1] = temp; } } 现在注意,我们给出O方法的定义: 若存在一常量K和起点n0,使当n>=n0时,有f(n)<=K*g(n),则f(n)= O(g(n))。(呵呵,不要说没学好数学呀,对于编程数学是非常重要的!!) 现在我们来看1/2*(n-1)*n,当K=1/2,n0=1,g(n)=n*n时,1/2*(n-1)*n<=1/2*n*n=K*g(n)。所以f(n)=O(g(n))=O(n*n)。所以我们程序循环的复杂度为O(n*n)。 二、交换排序 【稳定的】 void ExchangeSort(int *a,int Count){ int temp;for(int i=0;i for(int j=i+1;j if(a[j] < a[i]) { temp = a[j]; a[j] = a[i]; a[i] = temp; } } 时间复杂度为O(n*n)。 三、选择法 【不稳定的】 void SelectSort(int *a,int Count){ int temp;//一个存储值 int pos;//一个存储下标 for(int i=0;i temp = a[i]; pos = i; for(int j=i+1;j if(a[j] < temp)//选择排序法就是用第一个元素与最小的元素交换 { temp = a[j]; pos = j;//下标的交换赋值,记录当前最小元素的下标位置 } a[pos] = a[i]; a[i] = temp;} } 遗憾的是算法需要的循环次数依然是1/2*(n-1)*n。所以算法复杂度为O(n*n)。 我们来看他的交换。由于每次外层循环只产生一次交换(只有一个最小值)。所以f(n)<=n 所以我们有f(n)=O(n)。所以,在数据较乱的时候,可以减少一定的交换次数。 四、插入法 【稳定的】 void InsertSort(int *a,int Count){ int temp;//一个存储值 int pos;//一个存储下标 for(int i=1;i { temp = a[i];//当前要插入的元素 pos = i-1; while(pos>=0 && temp { a[pos+1] = a[pos];//将前一个元素后移一位 pos--; } a[pos+1] = temp;} } 其复杂度仍为O(n*n)。 最终,我个人认为,在简单排序算法中,直接插入排序是最好的。 五、希尔排序法 【不稳定的】 /* * 希尔排序,n为数组的个数 */ void ShellSort(int arr[], int n){ int temp,pos;int d = n;//增量初值 do{ d = d/3 + 1; for(int i= d;i { temp = arr[i]; pos = i-d; while(pos>=0 && temp < arr[pos]){ arr[ pos + d ] = arr[pos]; pos-= d; } arr[ pos + d ] = temp; } } while(d > 1);} //实现增量为d的插入排序 三种高级排序算法 一、快速排序 辅助空间复杂度为O(1) 【不稳定的】 void QuickSort(int *a,int left, int right){ int i,j,middle,temp;i = left;j = right;middle = a[(left+right)/2 ];do { while(a[i] i++; while(a[j]>middle && j>left)//从右扫描小于中值的数 j--; if(i<=j)//找到了一对值 { temp = a[i]; a[i] = a[j]; } a[j] = temp; i++; j--;} } while(i 注意,在扫描过程中,对于给定参考值,对于向右(左)扫描,如果扫描值大(小)于或等于参考值,就需要进行交换。最终得到的结果是,j左边的值都小于参考值,而i右边的值都大于参考值,j和i之间的值都等于参考值。对j左边和i右边的分别使用递归,就可以完成最终的排序。 这里我没有给出行为的分析,因为这个很简单,我们直接来分析算法:首先我们考虑最理想的情况 1.数组的大小是2的幂,这样分下去始终可以被2整除。假设为2的k次方,即k=log2(n)。 2.每次我们选择的值刚好是中间值,这样,数组才可以被等分。 第一层递归,循环n次,第二层循环2*(n/2)......所以共有n+2(n/2)+4(n/4)+...+n*(n/n)= n+n+n+...+n=k*n=log2(n)*n 所以算法复杂度为O(log2(n)*n) 其他的情况只会比这种情况差,最差的情况是每次选择到的middle都是最小值或最大值,那么他将变 成交换法(由于使用了递归,情况更糟),但是糟糕的情况只会持续一个流程,到下一个流程的时候就很可能已经避开了该中间的最大和最小值,因为数组下标变化了,于是中间值不在是那个最大或者最小值。但是你认为这种情况发生的几率有多大??呵呵,你完全不必担心这个问题。实践证明,大多数的情况,快速排序总是最好的。 如果你担心这个问题,你可以使用堆排序,这是一种稳定的O(log2(n)*n)算法,但是通常情况下速度要慢 于快速排序(因为要重组堆)。 二、归并排序(两种实现方法均要掌握) 【稳定的】 归并排序是一种极好的外部排序方法,即针对数据保存在磁盘上而不是高速内存中的问题。 //以下程序参考数据结构课本P286页的模板,为使用指针链表实现的 #include struct node{ //链表的节点数据 int value;node *next;}; node * divide_from(node * head){ node * position, * midpoint, * second_half;if((midpoint=head)== NULL)//List is empty return NULL;position = midpoint->next;while(position!= NULL)//Move position twice for midpoint's one move { position = position->next; if(position!= NULL) { midpoint = midpoint->next; position = position->next; } } second_half = midpoint->next;midpoint->next = NULL;//在这里将原链拆断,分为两段 return second_half;} node * merge(node * first, node * second){ node * last_sorted;//当前已经链接好的有序链中的最后一个节点 node combined;//哑节点 last_sorted = &combined;while(first!=NULL && second!=NULL){ if(first->value < second->value){ last_sorted->next = first; last_sorted = first; first = first->next; }else { last_sorted->next = second; last_sorted = second; second = second->next; } } if(first==NULL) last_sorted->next = second;else last_sorted->next = first;return combined.next;//返回哑节点的后继指针,即为合并后的链表的头指针 } //这里的参数必须是引用调用,需要这个指引去允许函数修改调用自变量 void MergeSort(node * &head){ if(head!= NULL && head->next!= NULL)//如果只有一个元素,则不需排序 { node * second_half = divide_from(head); MergeSort(head); MergeSort(second_half); head = merge(head, second_half);} } int main(){ node a,b,c,d;node *p1, *p2, *p3, *p4,*head;p1 = &a;p2 = &b;p3 = &c;p4 = &d;a.value = 2;b.value = 4;c.value = 3;d.value = 1;a.next = p2;b.next = p3;c.next = p4;d.next = NULL;//调用归并排序前的结果 head = p1;while(head!= NULL){ cout< head = head->next;} cout< head = p1;while(head!= NULL){ cout< head = head->next;} cout< //以下程序为使用数组实现的归并排序,辅助空间复杂度为O(n) #include void Merge(int data[], int left, int mid, int right){ int n1,n2,k,i,j;n1 = midmid;int *L = new int[n1];//两个指针指向两个动态数组的首地址 int *R = new int[n2];for(i=0,k=left;i L[i] = data[k];for(i=0,k=mid+1;i R[i] = data[k];for(k=left,i=0,j=0;i if(L[i] < R[j]){ //取小者放前面 data[k] = L[i]; i++; } else { data[k] = R[j]; j++; } } if(i for(j=i;j < n1;j++,k++) } /* * left:数组的开始下标,一般为0;right:数组的结束下标,一般为(n-1)*/ void MergeSort(int data[], int left, int right){ if(left < right){ int mid = left +(right-left)/ 2;//mid=(right+left)/2,防止溢出 MergeSort(data, left, mid); MergeSort(data , mid+1, right); Merge(data , left, mid , right);} } int main(){ int data[] = {9,8,7,2,5,6,3,55,1};//排序前的输出 for(int i=0;i<9;i++) cout< for(int i=0;i<9;i++) cout< 三、堆排序 【不稳定的】 /* * 向堆中插入current元素的函数 */ void insert_heap(int data[], const int ¤t, int low, int high) data[k] = L[j];else //if(j for(i=j;i data[k] = R[i];delete []L;//回收内存 delete []R;{ int large;//元素data[low]左右儿子中,大者的位置 large = 2*low + 1;while(large <= high){ if(large < high && data[large] < data[ large+1]) large++; if(current > data[ large ])//待插入元素的值比它的两个儿子都大 break; else { data[ low ] = data[ large ];//将其左右儿子的大者上移 low = large; large = 2 * large + 1; } } data[ low ] = current;} /* * 建立堆函数,num为数组data的元素个数 * 只有一个结点的<2-树>自动满足堆的属性,因此不必担心树中的任何树叶,即 * 不必担心表的后一半中的元素。如果从表的中间点开始并从后向前工作,就 * 能够使用函数insert_heap去将每个元素插入到包含了所有后面元素的部分堆 * 中,从而创建完整的堆。*/ void build_heap(int data[], int num){ int current;for(int low = num/2-1;low>=0;low--){ current = data[ low ]; insert_heap(data, current, low, num-1);} } /* * 堆排序主函数,num为数组data的元素个数 */ void heap_sort(int data[], int num){ int current, last_sorted;build_heap(data, num);//建立堆 for(last_sorted = num-1;last_sorted>0;last_sorted--){ //逐个元素处理 current = data[ last_sorted ];//data[0]在整个数组排序结束前,存储的是待排序元素中最大的元素 data[last_sorted] = data[0]; insert_heap(data, current, 0, last_sorted-1);} } int main(){ //用于排序算法的输入输出 int a[8] = {5,7,1,2,9,4,6,3,};for(int i=0;i< sizeof(a)/sizeof(int);i++) cout< for(int i=0;i< sizeof(a)/sizeof(int);i++) cout< 排序算法总结 所谓排序,就是要整理文件中的记录,使之按关键字递增(或递减)次序排列起来。当待排序记录的关键字都不相同时,排序结果是惟一的,否则排序结果不惟一。 在待排序的文件中,若存在多个关键字相同的记录,经过排序后这些具有相同关键字的记录之间的相对次序保持不变,该排序方法是稳定的;若具有相同关键字的记录之间的相对次序发生改变,则称这种排序方法是不稳定的。 要注意的是,排序算法的稳定性是针对所有输入实例而言的。即在所有可能的输入实例中,只要有一个实例使得算法不满足稳定性要求,则该排序算法就是不稳定的。 一.插入排序 插入排序的基本思想是每步将一个待排序的记录按其排序码值的大小,插到前面已经排好的文件中的适当位置,直到全部插入完为止。插入排序方法主要有直接插入排序和希尔排序。 ①.直接插入排序(稳定)接插入排序的过程为:在插入第i个记录时,R1,R2,..Ri-1已经排好序,将第i个记录的排序码Ki依次和R1,R2,..,Ri-1的排序码逐个进行比较,找到适当的位置。使用直接插入排序,对于具有n个记录的文件,要进行n-1趟排序。 代码如下: void Dir_Insert(int A[],int N)//直接插入排序 { int j,t;for(int i=1;i 希尔(Shell)排序的基本思想是:先取一个小于n的整数d1作为第一个增量把文件的全部记录分成d1个组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取得第二个增量d2 一般取d1=n/2,di+1=di/2。如果结果为偶数,则加1,保证di为奇数。 希尔排序是不稳定的,希尔排序的执行时间依赖于增量序列,其平均时间复杂度为O(n^1.3).代码如下: void Shell(int A[],int n)//Shell排序 { int i,j,k,t;(n/2)%2 == 0 ? k = n/2+1 : k = n/2;//保证增量为奇数 while(k > 0){ for(j=k;j 二.选择排序 选择排序的基本思想是每步从待排序的记录中选出排序码最小的记录,顺序存放在已排序的记录序列的后面,直到全部排完。选择排序中主要使用直接选择排序和堆排 序。 ①.直接选择排序(不稳定) 直接选择排序的过程是:首先在所有记录中选出序码最小的记录,把它与第1个记录交换,然后在其余的记录内选出排序码最小的记录,与第2个记录交换......依次类推,直到所有记录排完为止。 无论文件初始状态如何,在第i趟排序中选出最小关键字的记录,需要做n-i次比较,因此,总的比较次数为n(n-1)/2=O(n^2)。当初始文件为正序时,移动次数为0;文件初态为反序时,每趟排序均要执行交换操作,总的移动次数取最大值3(n-1)。直接选择排序的平均时间复杂度为O(n^2)。直接选择排序是不稳定的。 代码如下: void Dir_Choose(int A[],int n)//直接选择排序 { int k,t;for(int i=0;i ②.堆排序(不稳定) 堆排序是一种树形选择排序,是对直接选择排序的有效改进。n个关键字序列 K1,K2,...,Kn称为堆,当且仅当该序列满足(Ki<=K2i且Ki<=K2i+1)或(Ki>=K2i且Ki>=K2i+1),(1<=i<=n/2)。根结点(堆顶)的关键字是堆里所有结点关键字中最小者,称为小根堆;根结点的关键字是堆里所有结点关键字中最大者,称为大根堆。若将此序列所存储的向量R[1..n]看作是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:树中任一非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。 堆排序的关键步骤有两个:一是如何建立初始堆;二是当堆的根结点与堆的最后一个结点交换后,如何对少了一个结点后的结点序列做调整,使之重新成为堆。堆排序的最坏时间复杂度为O(nlog2n),堆排序的平均性能较接近于最坏性能。由于建初始堆所需的比较 次数较多,所以堆排序不适宜于记录较少的文件。堆排序是就地排序,辅助空间为O(1),它是不稳定的排序方法。 代码略..三.交换排序 交换排序的基本思想是:两两比较待排序记录的排序码,并交换不满足顺序要求的那写偶对,直到满足条件为止。交换排序的主要方法有冒泡排序和快速排序.①.冒泡排序(稳定的) 冒泡排序将被排序的记录数组R[1..n]垂直排列,每个记录R[i]看作是重量为ki的气泡。根据轻气泡不能在重气泡之下的原则,从下往上扫描数组R;凡扫描到违反本原则的轻气泡,就使其向上“漂浮”。如此反复进行,直到最后任何两个气泡都是轻者在上,重者在下为止。 冒泡排序的具体过程如下: 第一步,先比较k1和k2,若k1>k2,则交换k1和k2所在的记录,否则不交换。继续对k2和k3重复上述过程,直到处理完kn-1和kn。这时最大的排序码记录转到了最后位置,称第1次起泡,共执行n-1次比较。 与第一步类似,从k1和k2开始比较,到kn-2和kn-1为止,共执行n-2次比较。 依次类推,共做n-1次起泡,完成整个排序过程。 若文件的初始状态是正序的,一趟扫描即可完成排序。所需关键字比较次数为n-1次,记录移动次数为0。因此,冒泡排序最好的时间复杂度为O(n)。 若初始文件是反序的,需要进行n-1趟排序。每趟排序要进行n-i次关键字的比较(1<=i<=n-1),且每次比较都必须移动记录三次来达到交换记录位置。在这种情况下,比较次数达到最大值n(n-1)/2=O(n^2),移动次数也达到最大值3n(n-1)/2=O(n^2)。因此,冒泡排序的最坏时间复杂度为O(n^2)。 虽然冒泡排序不一定要进行n-1趟,但由于它的记录移动次数较多,故平均性能比直接插入排序要差得多。冒泡排序是就地排序,且它是稳定的。 代码如下: void QP(int A[],int n)//优化的冒泡排序 { int count=0,t,flag;for(int i=0;i ②.快速排序:(不稳定的) 快速排序采用了一种分治的策略,通常称其为分治法,其基本思想是:将原问题分解为若干个规模更小但结构与原问题相似的子问题。递归地解这些子问题,然后将这些子问题的解组合为原问题的解。 快速排序的具体过程如下: 第一步,在待排序的n个记录中任取一个记录,以该记录的排序码为准,将所有记录分成两组,第1组各记录的排序码都小于等于该排序码,第2组各记录的排序码都大于该排序码,并把该记录排在这两组中间。 第二步,采用同样的方法,对左边的组和右边的组进行排序,直到所有记录都排到相应的位置为止。 代码如下: void Quick_Sort(int A[],int low,int high)//low和high是数组的下标 { if(low 四.归并排序 归并排序是将两个或两个以上的有序子表合并成一个新的有序表。初始时,把含有n个结点的待排序序列看作由n个长度都为1的有序子表组成,将它们依次两两归并得到长度为2的若干有序子表,再对它们两两合并。直到得到长度为n的有序表,排序结束。 归并排序是一种稳定的排序,可用顺序存储结构,也易于在链表上实现,对长度为n的文件,需进行log2n趟二路归并,每趟归并的时间为O(n),故其时间复杂度无论是在最好情况下还是在最坏情况下均是O(nlog2n)。归并排序需要一个辅助向量来暂存两个有序子文件归并的结果,故其辅助空间复杂度为O(n),显然它不是就地排序。 代码略...五.基数排序 设单关键字的每个分量的取值范围均是C0<=Kj<=Crd-1(0<=j<=rd),可能的取值个数rd称为基数.基数的选择和关键字的分解因关键字的类型而异. (1).若关键字是十进制整数,则按个、十等位进行分解,基数rd=10,C0=0,C9=9,d为最长整数的位数. (2).若关键字是小写的英文字符串,则rd=26,C0='a',C25='z',d为最长字符串的长度. 基数排序的基本思想是:从低位到高位依次对待排序的关键码进行分配和收集,经过d趟分配和收集,就可以得到一个有序序列. 按平均时间将排序分为四类: (1)平方阶(O(n2))排序 一般称为简单排序,例如直接插入、直接选择和冒泡排序; (2)线性对数阶(O(nlgn))排序 如快速、堆和归并排序; (3)O(n1+£)阶排序 £是介于0和1之间的常数,即0<£<1,如希尔排序; (4)线性阶(O(n))排序 如基数排序。 各种排序方法比较 简单排序中直接插入最好,快速排序最快,当文件为正序时,直接插入和冒泡均最佳。 影响排序效果的因素 因为不同的排序方法适应不同的应用环境和要求,所以选择合适的排序方法应综合考虑下列因素: ①待排序的记录数目n; ②记录的大小(规模); ③关键字的结构及其初始状态; ④对稳定性的要求; ⑤语言工具的条件; ⑥存储结构; ⑦时间和辅助空间复杂度等。 不同条件下,排序方法的选择 (1)若n较小(如n≤50),可采用直接插入或直接选择排序。 当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。 (2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜;(3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或 归并排序。 快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短; 堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。 若要求排序稳定,则可选用归并排序。但从单个记录起进行两两归并的 排序算法并不值得提倡,通常可以将它和直接插入排序结合在一起使用。先利用直接插入排序求得较长的有序子文件,然后再两两归并之。因为直接插入排序是稳定的,所以改进后的归并排序仍是稳定的。 www.xiexiebang.com 用php实现的各种排序算法总结 优化php性能的五个实用技巧: 以下是五个优化技巧,熟练掌握后对于开发还是很有帮助的。 1.对字符串使用单引号 PHP 引擎允许使用单引号和双引号来封装字符串变量,但是这个是有很大的差别的!使用双引号的字符串告诉 PHP 引擎首先去读取字符串内容,查找其中的变量,并改为变量对应的值。一般来说字符串是没有变量的,所以使用双引号会导致性能不佳。最好是使用字符串连接而不 是双引号字符串。 BAD: $output = “This is a plain string”; GOOD: $output = 'This is a plain string'; BAD: $type = “mixed”; $output = “This is a $type string”; GOOD: $type = 'mixed'; $output = 'This is a '.$type.' string'; 2.不要随便就复制变量 有时候为了使 PHP 代码更 加整洁,一些 PHP 新手(包括我)会把预定义好的变量复制到一个名字更简短的变量中,其实这样做的结果是增加了一倍的内存消耗,只会使程序更加慢。试想一下,在下面的例子 中,如果用户恶意插入 512KB 字节的文字到文本输入框中,这样就会导致 1MB 的内存被消耗! BAD: $description = $_POST['description'];shishicaimh.com www.xiexiebang.com echo $description; GOOD: echo $_POST['description']; 3.使用 echo 函数来输出字符串 使用 echo()函数来打印结果出了有更容易阅读之外,在下个例子中,你还可以看到有更好的性能。 BAD: print($myVariable); GOOD: echo $myVariable; 4.不要在 echo 中使用连接符 很***PHP 程序员(有包括我)不知道在用 恶臭 输出多个变量的时候,其实可以使用逗号来分开的,而不必用字符串先把他们先连起来,如下面的第一个例子中,由于使用了连接符就会有性能问题,因为这样就会 需要 PHP 引擎首先把所有的变量连接起来,然后在输出,而在第二个例子中,PHP 引擎就会按照循序输出他们。 BAD: echo 'Hello, my name is'.$firstName.$lastName.' and I live in '.$city; GOOD: echo 'Hello, my name is' , $firstName , $lastName , ' and I live in ' , $city; 5.使用 switch/case 代替 if/else 对于只有单个变量的判断,使用 switch/case 语句而不是 if/else 语句,会有更好的性能,并且代码更加容易阅读和维护。 BAD: if($_POST['action'] == 'add‘){ shishicaimh.com www.xiexiebang.com addUser(); } elseif($_POST['action'] == 'delete’){ deleteUser(); } elseif($_POST['action'] == 'edit‘){ editUser(); } else { defaultAction(); } GOOD: switch($_POST['action']){ case 'add': addUser(); break; case 'delete': 用php实现的各种排序算法,冒泡排序,交换排序,选择法排序,插入法排序,快速排序,根据实际情况可选则不同的排序算法。效率也有所不同。 冒泡排序: function BubbleSort($arr){ $num = count($arr); for($i=1;$i<$num;$i++){ for($j=$num-1;$j>=$i;$j--){ if($arr[$j]<$arr[$j-1]){ $iTemp = $arr[$j-1]; $arr[$j-1] = $arr[$j]; $arr[$j] = $iTemp; } } } return $arr; } ?> shishicaimh.com www.xiexiebang.com 交换法排序: function ExchangeSort($arr){ $num = count($arr); for($i=0;$i<$num-1;$i++){ for($j=$i+1;$j<$num;$j++){ if($arr[$j]<$arr[$i]){ $iTemp = $arr[$i]; $arr[$i] = $arr[$j]; $arr[$j] = $iTemp; } } } return $arr; } ?> 选择法排序: function SelectSort($arr){ $num = count($arr); for($i=0;$i<$num-1;$i++){ $iTemp = $arr[$i]; $iPos = $i; for($j=$i+1;$j<$num;$j++){ if($arr[$j]<$iTemp){ $iTemp = $arr[$j]; $iPos = $j; } } $arr[$iPos] = $arr[$i]; $arr[$i] = $iTemp; } return $arr; } ?> 插入法排序: function InsertSort($arr){ $num = count($arr); for($i=1;$i<$num;$i++){ $iTemp = $arr[$i]; $iPos = $i-1; while(($iPos>=0)&&($iTemp<$arr[$iPos])){ $arr[$iPos+1] = $arr[$iPos]; $iPos--; } $arr[$iPos+1] = $iTemp;shishicaimh.com www.xiexiebang.com } return $arr; } ?> 快速排序 : function QuickSort($arr){ $num = count($arr); $l=$r=0; for($i=1;$i<$num;$i++){ if($arr[$i] < $arr[0]){ $left[] = $arr[$i]; $l++; }else{ $right[] = $arr[$i]; $r++; } } if($l > 1){ $left = QuickSort($left); } $new_arr = $left; $new_arr[] = $arr[0]; if($r > 1){ $right = QuickSort($right); } for($i=0;$i<$r;$i++){ $new_arr[] = $right[$i]; } return $new_arr; } $arr = array(7,1,6,5,2); $arr_new = QuickSort($arr); ?> deleteUser(); break; case 'edit': editUser(); break; default: shishicaimh.com 事先声明,此文档来自某技术论坛,内容归原作者所有。 1.基本思想: 每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在已排好序的数列的最后,直到全部待排序的数据元素排完。2.排序过程: 【示例】: 初始关键字 [49 38 65 97 76 13 27 49] 第一趟排序后 13 [38 65 97 76 49 27 49] 第二趟排序后 13 27 [65 97 76 49 38 49] 第三趟排序后 13 27 38 [97 76 49 65 49] 第四趟排序后 13 27 38 49 [49 97 65 76] 第五趟排序后 13 27 38 49 49 [97 97 76] 第六趟排序后 13 27 38 49 49 76 [76 97] 第七趟排序后 13 27 38 49 49 76 76 [ 97] 最后排序结果 13 27 38 49 49 76 76 97 3.void selectionSort(Type* arr,long len){ long i=0,j=0;/*iterator value*/ long maxPos;assertF(arr!=NULL,“In InsertSort sort,arr is NULLn”);for(i=len-1;i>=1;i--){ maxPos=i;for(j=0;j 插入排序(Insertion Sort)的基本思想是:每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子文件中的适当位置,直到全部记录插入完成为止。直接插入排序 直接插入排序(Straight Insertion Sort):将一个记录插入到排好序的有序表中,从而得到一个新的、记录数增1的有序表。直接插入排序算法 哨兵(监视哨)有两个作用:一是作为临变量存放R[i](当前要进行比较的关键字)的副本;二是在查找循环中用来监视下标变量j是否越界。 当文件的初始状态不同时,直接插入排序所耗费的时间是有很大差异的。最好情况是文件初态为正序,此时算法的时间复杂度为O(n),最坏情况是文件初态为反序,相应的时间复杂度为O(n2),算法的平均时间复杂度是O(n2)。算法的辅助空间复杂度是O(1),是一个就地排序。 直接插入排序是稳定的排序方法。三.冒泡排序 [算法思想]:将被排序的记录数组R[1..n]垂直排列,每个记录R[i]看作是重量为R[i].key的气泡。根据轻气泡不能在重气泡之下的原则,从下往上扫描数组R:凡扫描到违反本原则的轻气泡,就使其向上“飘浮”。如此反 复进行,直到最后任何两个气泡都是轻者在上,重者在下为止。 [算法]: void BubbleSort(SeqList R){ //R(l..n)是待排序的文件,采用自下向上扫描,对R做冒泡排序 int i,j; Boolean exchange; //交换标志 for(i=1;i exchange=FALSE; //本趟排序开始前,交换标志应为假 for(j=n-1;j>=i;j--)//对当前无序区R[i..n]自下向上扫描 if(R[j+1].key R[0]=R[j+1]; //R[0]不是哨兵,仅做暂存单元 R[j+1]=R[j]; R[j]=R[0]; exchange=TRUE; //发生了交换,故将交换标志置为真 } if(!exchange)return;//本趟排序未发生交换,提前终止算法 } //endfor(外循环)} //BubbleSort [分析]:起泡排序的结束条件为:最后一趟没有进行“交换”。从起泡排序的过程可见,起泡排序是一个增加有序序列长度的过程,也是一个缩小无序序列长度的过程,每经过一趟起泡,无序序列的长度只缩小1。[算法思想]:将被排序的记录数组R[1..n]垂直排列,每个记录R[i]看作是重量为R[i].key的气泡。根据轻气泡不能在重气泡之下的原则,从下往上扫描数组R:凡扫描到违反本原则的轻气泡,就使其向上“飘浮”。如此反复进行,直到最后任何两个气泡都是轻者在上,重者在下为止。 [算法]: void BubbleSort(SeqList R){ //R(l..n)是待排序的文件,采用自下向上扫描,对R做冒泡排序 int i,j; Boolean exchange; //交换标志 for(i=1;i exchange=FALSE; //本趟排序开始前,交换标志应为假 for(j=n-1;j>=i;j--)//对当前无序区R[i..n]自下向上扫描 if(R[j+1].key R[0]=R[j+1]; //R[0]不是哨兵,仅做暂存单元 R[j+1]=R[j]; R[j]=R[0]; exchange=TRUE; //发生了交换,故将交换标志置为真 } if(!exchange)return;//本趟排序未发生交换,提前终止算法 } //endfor(外循环)} //BubbleSort [分析]:起泡排序的结束条件为:最后一趟没有进行“交换”。从起泡排序的过程可见,起泡排序是一个增加有序序列长度的过程,也是一个缩小无序序列长度的过程,每经过一趟起泡,无序序列的长度只缩小1。四.希尔排序 基本思想: 先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为d l的倍数的记录放在同一个组中。先在各组内进行直接插人排序;然后,取第二个增量d2 该方法实质上是一种分组插入方法。给定实例的shell排序的排序过程 假设待排序文件有10个记录,其关键字分别是: 49,38,65,97,76,13,27,49,55,04。 增量序列的取值依次为: 5,3,1 Shell排序的算法实现 1. 不设监视哨的算法描述 void ShellPass(SeqList R,int d){//希尔排序中的一趟排序,d为当前增量 for(i=d+1;i<=n;i++)//将R[d+1..n]分别插入各组当前的有序区 if(R[i].key R[j+d];=R[j]; //后移记录 j=j-d; //查找前一记录 }while(j>0&&R[0].key R[j+d]=R[0]; //插入R[i]到正确的位置上 } //endif } //ShellPass void ShellSort(SeqList R){ int increment=n; //增量初值,不妨设n>0 do { increment=increment/3+1; //求下一增量 ShellPass(R,increment); //一趟增量为increment的Shell插入排序 }while(increment>1)} //ShellSort 注意: 当增量d=1时,ShellPass和InsertSort基本一致,只是由于没有哨兵而在内循环中增加了一个循环判定条件“j>0”,以防下标越界。2.设监视哨的shell排序算法 算法分析 1.增量序列的选择 Shell排序的执行时间依赖于增量序列。 好的增量序列的共同特征: ① 最后一个增量必须为1; ② 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况。 有人通过大量的实验,给出了目前较好的结果:当n较大时,比较和移动的次数约在nl.25到1.6n1.25之间。 2.Shell排序的时间性能优于直接插入排序 希尔排序的时间性能优于直接插入排序的原因: ①当文件初态基本有序时直接插入排序所需的比较和移动次数均较少。 ②当n值较小时,n和n2的差别也较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度0(n2)差别不大。 ③在希尔排序开始时增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量di逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按di-1作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。 因此,希尔排序在效率上较直接插人排序有较大的改进。3.稳定性 希尔 排序是不稳定的。参见上述实例,该例中两个相同关键字49在排序前后的相对次序发生了变化。五.堆排序 1、堆排序定义 n个关键字序列Kl,K2,„,Kn称为堆,当且仅当该序列满足如下性质(简称为堆性质): (1)ki≤K2i且ki≤K2i+1 或(2)Ki≥K2i且ki≥K2i+1(1≤i≤) 若将此序列所存储的向量R[1..n]看做是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:树中任一非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。 【例】关键字序列(10,15,56,25,30,70)和(70,56,30,25,15,10)分别满足堆性质(1)和(2),故它们均是堆,其对应的完全二叉树分别如小根堆示例和大根堆示例所示。 2、大根堆和小根堆 根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者的堆称为小根堆。 根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆。注意: ①堆中任一子树亦是堆。 ②以上讨论的堆实际上是二叉堆(Binary Heap),类似地可定义k叉堆。 3、堆排序特点 堆排序(HeapSort)是一树形选择排序。 堆排序的特点是:在排序过程中,将R[l..n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系【参见二叉树的顺序存储结构】,在当前无序区中选择关键字最大(或最小)的记录。 4、堆排序与直接插入排序的区别 直接选择排序中,为了从R[1..n]中选出关键字最小的记录,必须进行n-1次比较,然后在R[2..n]中选出关键字最小的记录,又需要做n-2次比较。事实上,后面的n-2次比较中,有许多比较可能在前面的n-1次比较中已经做过,但由于前一趟排序时未保留这些比较结果,所以后一趟排序时又重复执行了这些比较操作。 堆排序可通过树形结构保存部分比较结果,可减少比较次数。 5、堆排序 堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。(1)用大根堆排序的基本思想 ① 先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区 ② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key ③ 由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有 序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。 „„ 直到无序区只有一个元素为止。(2)大根堆排序算法的基本操作: ① 初始化操作:将R[1..n]构造为初始堆; ② 每一趟排序的基本操作:将当前无序区的堆顶记录R[1]和该区间的最后一个记录交换,然后将新的无序区调整为堆(亦称重建堆)。注意: ①只需做n-1趟排序,选出较大的n-1个关键字即可以使得文件递增有序。 ②用小根堆排序与利用大根堆类似,只不过其排序结果是递减有序的。堆排序和直接选择排序相反:在任何时刻,堆排序中无序区总是在有序区之前,且有序区是在原向量的尾部由后往前逐步扩大至整个向量为止。(3)堆排序的算法: void HeapSort(SeqIAst R){ //对R[1..n]进行堆排序,不妨用R[0]做暂存单元 int i; BuildHeap(R); //将R[1-n]建成初始堆 for(i=n;i>1;i--){ //对当前无序区R[1..i]进行堆排序,共做n-1趟。R[0]=R[1];R[1]=R[i];R[i]=R[0]; //将堆顶和堆中最后一个记录交换 Heapify(R,1,i-1); //将R[1..i-1]重新调整为堆,仅有R[1]可能违反堆性质 } //endfor } //HeapSort(4)BuildHeap和Heapify函数的实现 因为构造初始堆必须使用到调整堆的操作,先讨论Heapify的实现。① Heapify函数思想方法 每趟排序开始前R[l..i]是以R[1]为根的堆,在R[1]与R[i]交换后,新的无序区R[1..i-1]中只有R[1]的值发生了变化,故除R[1]可能违反堆性质外,其余任何结点为根的子树均是堆。因此,当被调整区间是R[low..high]时,只须调整以R[low]为根的树即可。“筛选法”调整堆 R[low]的左、右子树(若存在)均已是堆,这两棵子树的根R[2low]和R[2low+1]分别是各自子树中关键字最大的结点。若R[low].key不小于这两个孩子结点的关键字,则R[low]未违反堆性质,以R[low]为根的树已是堆,无须调整;否则必须将R[low]和它的两个孩子结点中关键字较大者进行交换,即R[low]与R[large](R[large].key=max(R[2low].key,R[2low+1].key))交换。交换后又可能使结点R[large]违反堆性质,同样由于该结点的两棵子树(若存在)仍然是堆,故可重复上述的调整过程,对以R[large]为根的树进行调整。此过程直至当前被调整的结点已满足堆性质,或者该结点已是叶子为止。上述过程就象过筛子一样,把较小的关键字逐层筛下去,而将较大的关键字逐层选上来。因此,有人将此方法称为“筛选法”。 ②BuildHeap的实现 要将初始文件R[l..n]调整为一个大根堆,就必须将它所对应的完全二叉树中以每一结点为根的子树都调整为堆。 显然只有一个结点的 树是堆,而在完全二叉树中,所有序号 的结点都是叶子,因此以这些结点为根的子树均已是堆。这样,我们只需依次将以序号为,-1,„,1的结点作为根的子树都调整为堆即可。 具体算法【参见教材】。 5、大根堆排序实例 对于关键字序列(42,13,24,91,23,16,05,88),在建堆过程中完全二叉树及其存储结构的变化情况参见。 6、算法分析 堆排序的时间,主要由建立初始堆和反复重建堆这两部分的时间开销构成,它们均是通过调用Heapify实现的。 堆排序的最坏时间复杂度为O(nlgn)。堆排序的平均性能较接近于最坏性能。 由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。 堆排序是就地排序,辅助空间为O(1),它是不稳定的排序方法。六.快速排序 快速排序的基本思路是:首先我们选择一个中间值middle(程序中我们可使用数组中间值),把比中间值小的放在其左边,比中间值大的放在其右边。由于这个排序算法较复杂,我们先给出其进行一次排序的程序框架(从各类数据结构教材中可得): void QuickSort(int *pData, int left, int right){ int i, j;int middle, iTemp;i = left;j = right;middle = pData[(left + right)/ 2];//求中间值 do { while((pData[i] < middle)&&(i < right))//从左扫描大于中值的数 i++; while((pData[j] > middle)&&(j > left))//从右扫描小于中值的数 j--; if(i <= j)//找到了一对值 { //交换 iTemp = pData[i]; pData[i] = pData[j]; pData[j] = iTemp; i++; j--; } } while(i <= j);//如果两边扫描的下标交错,就停止(完成一次) //当左边部分有值(left if(left QuickSort(pData,left,j); //当右边部分有值(right>i),递归右半边 if(right>i) QuickSort(pData,i,right);} 对于n个成员,快速排序法的比较次数大约为n*logn 次,交换次数大约为(n*logn)/6次。如果n为100,冒泡法需要进行4950 次比较,而快速排序法仅需要200 次,快速排序法的效率的确很高。快速排序法的性能与中间值的选定关系密切,如果每一次选择的中间值都是最大值(或最小值),该算法的速度就会大大下降。快速排序算法最坏情况下的时间复杂度为O(n2),而平均时间复杂度为O(n*logn)。七.合并排序 說明 之前所介紹的排序法都是在同一個陣列中的排序,考慮今日有兩筆或兩筆以上的資料,它可能是不同陣列中的資料,或是不同檔案中的資料,如何為它們進行排序? 解法 可以使用合併排序法,合併排序法基本是將兩筆已排序的資料合併並進行排序,如果所讀入的資料尚未排序,可以先利用其它的排序方式來處理這兩筆資料,然後再將排序好的這兩筆資料合併。 有人問道,如果兩筆資料本身就無排序順序,何不將所有的資料讀入,再一次進行排序?排序的精神是儘量利用資料已排序的部份,來加快排序的效率,小筆資料的排序較為快速,如果小筆資料排序完成之後,再合併處理時,因為兩筆資料都有排序了,所有在合併排序時會比單純讀入所有的資料再一次排序來的有效率。那麼可不可以直接使用合併排序法本身來處理整個排序的動作?而不動用到其它的排序方式?答案是肯定的,只要將所有的數字不斷的分為兩個等分,直到最後剩一個數字為止,然後再反過來不斷的合併,就如下圖所示: 不過基本上分割又會花去額外的時間,不如使用其它較好的排序法來排序小筆資料,再使用合併排序來的有效率。 下面這個程式範例,我們使用快速排序法來處理小筆資料排序,然後再使用合併排序法處理合併的動作。例子 C #include quicksort(number1, 0, MAX1-1);quicksort(number2, 0, MAX2-1);printf(“n排序後:”);printf(“nnumber1[]:”);for(i = 0;i < MAX1;i++)printf(“%d ”, number1[i]);printf(“nnumber2[]:”);for(i = 0;i < MAX2;i++)printf(“%d ”, number2[i]);// 合併排序 mergesort(number1, MAX1, number2, MAX2, number3);printf(“n合併後:”);for(i = 0;i < MAX1+MAX2;i++)printf(“%d ”, number3[i]);printf(“n”);return 0;} int partition(int number[], int left, int right){ int i, j, s;s = number[right];i = left-1;for(j = left;j < right;j++){ if(number[j] <= s){ i++;SWAP(number[i], number[j]);} } SWAP(number[i+1], number[right]);return i+1;} void quicksort(int number[], int left, int right){ int q;if(left < right){ q = partition(number, left, right);quicksort(number, left, q-1);quicksort(number, q+1, right);} } void mergesort(int number1[], int M, int number2[], int N, int number3[]){ int i = 0, j = 0, k = 0;while(i < M && j < N){ if(number1[i] <= number2[j])number3[k++] = number1[i++];else number3[k++] = number2[j++];} while(i < M)number3[k++] = number1[i++];while(j < N)number3[k++] = number2[j++];} Java public class MergeSort { public static int[] sort(int[] number1, int[] number2){ int[] number3 = new int[number1.length + number2.length];int i = 0, j = 0, k = 0;while(i < number1.length && j < number2.length){ if(number1[i] <= number2[j])number3[k++] = number1[i++];else number3[k++] = number2[j++];} while(i < number1.length)number3[k++] = number1[i++];while(j < number2.length)number3[k++] = number2[j++];return number3;} } 八。基数排序 基数排序是根据组成关键字的各位值,用“分配”和“收集”的方法进行排序。例如,把扑克牌的排序看成由花色和面值两个数据项组成的主关键字排序。 花色:梅花<方块<红心<黑桃 面值:2<3<4<...<10 若要将一副扑克牌排成下列次序: 梅花2,...,梅花A,方块2,...,方块A,红心2,...,红心A,黑桃2,...,黑桃A。 有两种排序方法: 一、先按花色分成四堆,把各堆收集起来;然后对每堆按面值由小到大排列,再按花色从小到大按堆收叠起来。----称为“最高位优先”(MSD)法。 二、先按面值由小到大排列成13堆,然后从小到大收集起来;再按花色不同分成四堆,最后顺序收集起来。----称为“最低位优先”(LSD)法。 [例] 设记录键值序列为{88,71,60,31,87,35,56,18},用基数排序(LSD)。如图所示:其中f[i]、e[i]为按位分配面值为i的队列的队头和队尾指针。 #define D 3 typedef struct { int key;float data;int link;} JD key data link int jspx(JD r[],int n){ /*链式存储表示的基数排序*/ int i,j,k,t,p,rd,rg,f[10],e[10];/*p为r[]的下标,rd,rg为比例因子,f[j],e[j]是代码为j的队的首尾指针*/ for(i=1;i 将每个记录项与其他诸项比较计算出小于该项的记录个数,以确定该项的位置。 操 作 系 统 实 验 报 告 (2)学院:计算机科学与技术学院 班级:计091 学号:姓名: 时间:2011/12/30 目 录 1.实验名称……………………………………………………3 2.实验目的……………………………………………………3 3.实验内容……………………………………………………3 4.实验要求……………………………………………………3 5.实验原理……………………………………………………3 6.实验环境……………………………………………………4 7.实验设计……………………………………………………4 7.1数据结构设计……………………………………………………………………4 7.2算法设计…………………………………………………………………………6 7.3功能模块设计……………………………………………………………………7 8.实验运行结果………………………………………………8 9.实验心得……………………………………………………9 附录:源代码(部分)…………………………………………………………………9 一、实验名称: 用C++实现银行家算法 二、实验目的: 通过自己编程来实现银行家算法,进一步理解银行家算法的概念及含义,提高对银行家算法的认识,同时提高自己的动手实践能力。 各种死锁防止方法能够阻止发生死锁,但必然会降低系统的并发性并导致低效的资源利用率。死锁避免却与此相反,通过合适的资源分配算法确保不会出现进程循环等待链,从而避免死锁。本实验旨在了解死锁产生的条件和原因,并采用银行家算法有效地防止死锁的发生。 三、实验内容: 利用C++,实现银行家算法 四、实验要求: 1.完成银行家算法的设计 2.设计有n个进程共享m个系统资源的系统,进程可动态的申请和释放资源,系统按各进程的申请动态的分配资源。 五、实验原理: 系统中的所有进程放入进程集合,在安全状态下系统收到进程的资源请求后,先把资源试探性的分配给它。之后,系统将剩下的可用资源和进程集合中的其他进程还需要的资源数作比较,找出剩余资源能够满足的最大需求量的进程,从而保证进程运行完毕并归还全部资源。这时,把这个进程从进程集合中删除,归还其所占用的所有资源,系统的剩余资源则更多,反复执行上述步骤。最后,检查进程集合,若为空则表明本次申请可行,系统处于安全状态,可以真正执行本次分配,否则,本次资源分配暂不实施,让申请资源的进程等待。 银行家算法是一种最有代表性的避免死锁的算法。在避免死锁方法中允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次分配资源的安全性,若分配不会导致系统进入不安全状态,则分配,否则等待。为实现银行家算法,系统必须设置若干数据结构。要解释银行家算法,必须先解释操作系统安全状态和不安全状态。安全序列是指一个进程序列{P1,…,Pn}是安全的,如果对于每一个进程Pi(1≤i≤n),它以后尚需要的资源量不超过系统当前剩余资源量与所有进程Pj(j < i)当前占有资源量之和。 安全状态:如果存在一个由系统中所有进程构成的安全序列P1,…,Pn,则系统处于安全状态。安全状态一定是没有死锁发生。 不安全状态:不存在一个安全序列。不安全状态不一定导致死锁。 我们可以把操作系统看作是银行家,操作系统管理的资源相当于银行家管理的资金,进程向操作系统请求分配资源相当于用户向银行家贷款。 为保证资金的安全,银行家规定: (1)当一个顾客对资金的最大需求量不超过银行家现有的资金时就可接纳该顾客; (2)顾客可以分期贷款,但贷款的总数不能超过最大需求量; (3)当银行家现有的资金不能满足顾客尚需的贷款数额时,对顾客的贷款可推迟支付,但总能使顾客在有限的时间里得到贷款; (4)当顾客得到所需的全部资金后,一定能在有限的时间里归还所有的资金.操作系统按照银行家制定的规则为进程分配资源,当进程首次申请资源时,要测试该进程对资源的最大需求量,如果系统现存的资源可以满足它的最大需求量则按当前的申请量分配资源,否则就推迟分配。当进程在执行中继续申请资源时,先测试该进程本次申请的资源数是否超过了该资源所剩余的总量。若超过则拒绝分配资源,若能满足则按当前的申请量分配资源,否则也要推迟分配。 六、实验环境: Win-7系统 Visual C++ 6.0 七、实验设计: 1.数据结构设计 定义结构体: struct Process//进程属性构成 { Source claim;//进程最大需求量 Source allocation;//进程占有量 Source claim_allocation;//进程需求量 Source currentAvail;//进程可获得量 }; 定义类对象: class Source //资源的基本构成以及功能 { private: public: int R1;//定义三类类资源 int R2;int R3; Source(int r1 = 0,int r2 = 0,int r3 = 0){ R1=r1;R2=r2;R3=r3;} Source(Source& s){ R1=s.R1;R2=s.R2;R3=s.R3;} void setSource(int r1 = 0,int r2 = 0,int r3 = 0)//设置各类资源 { R1=r1;R2=r2;R3=r3;} void add(Source s)//加法 { R1=R1+s.R1;R2=R2+s.R2;R3=R3+s.R3;} void sub(Source s)//减法 { R1=R1-s.R1;R2=R2-s.R2;R3=R3-s.R3;} bool lower(Source s)//比较 { if(R1 > s.R1)return false; if(R2 > s.R2)return false; if(R3 > s.R3)return false; return true;} }; class Data//封装所有数据 { public: Process *p;//进程指针 Source sum;//总资源量 Source available;//可获得量 Source ask;//请求量 int pLength;//进程个数 int * ruler;//逻辑尺 void clearProcess()//进程currentAvail清零 { for(int i=0;i { p[i].currentAvail.setSource(0, 0, 0);} } }; class DataInit//封装初始化数据函数类 { private: public: DataInit()//构造函数 { } void initLength(Data *db)//设置进程个数 { int n; cout<<“输入进程数: ”; cin>>n; db->pLength = n; db->p = new Process[n]; if(!db->p) {cout<<“error!no enough memory space!”;return;} db->ruler = new int[n]; if(!db->ruler) {cout<<“error!no enough memory space!”;return;} } 2.算法设计 class FindSafeList//寻找安全序列 { private: public: FindSafeList()//构造函数 {} bool checkList(Data *db)//检查一个序列安全性 { int i=0;//i用于循环 db->p[db->ruler[i]].currentAvail.add(db->available);//将当前系统可用资源量赋给该序列的第一个进程 if(!db->p[db->ruler[i]].claim_allocation.lower(db->p[db->ruler[i]].currentAvail))//若当前进程currentAvail小于该进程需求量(claim-allocation),返回false {return false;} for(i=1;i< db->pLength;i++) { //当前进程的可获得资源量currentAvail获得前一个进程的未释放资源前可获得资源量currentAvail db->p[db->ruler[i]].currentAvail.add(db->p[db->ruler[i-1]].currentAvail); //当前进程的可获得资源量currentAvail获得前一个进程的释放的资源量 db->p[db->ruler[i]].currentAvail.add(db->p[db->ruler[i-1]].allocation); //若当前进程currentAvail小于该进程需求量(claim-allocation),返回false if(!db->p[db->ruler[i]].claim_allocation.lower(db->p[db->ruler[i]].currentAvail)) { return false;} //若当前进程currentAvail大于该进程总资源量,返回false if(!db->p[db->ruler[i]].currentAvail.lower(db->sum)) { return false;} } return true;//该序列进程安全。返回true } bool exsitSafeList(Data *db)//判断是否存在安全序列 { int i = 0; for(i = 0;i < db->pLength;i++)//设置逻辑尺的刻度值 { db->ruler[i] = i;} while(1) //该循环将检测逻辑尺刻度值的全排列 { if(checkList(db)) //找到一个安全序列,返回true { return true;} db->clearProcess();//将所有进程的currentAvail清零 if(!next_permutation(db->ruler,db->ruler+db->pLength)) //所有排列完毕后退出生成排列库函数的调用 { return false; } } return false;} int findSafeList(Data *db, int i=0)//寻找安全序列 { //请求值大于系统当前可用资源值,返回0 if(!db->ask.lower(db->available)) { return 0;} //请求值大于当前进程需求资源值,返回1 if(!db->ask.lower(db->p[i].claim_allocation)) { return 1;} Source s(db->p[i].allocation);//根据请求,分配资源值 db->available.sub(db->ask); db->p[i].allocation.add(db->ask); db->p[i].claim_allocation.sub(db->ask); if(!exsitSafeList(db))//判断是否存在安全序列 { db->available.add(db->ask); //不存在安全序列,回滚,恢复分配前状态,并返回2 db->p[i].allocation.sub(db->ask); db->p[i].claim_allocation.add(db->ask); return 2; } db->ask.setSource(0,0,0);//找到安全序列,将请求资源置零,返回3 return 3;} };3.功能模块设计 class Data//封装所有数据 class DataInit//封装初始化数据函数类 class Display//封装显示方法 class FindSafeList//寻找安全序列 struct Process//进程属性构成 void main()//主函数 八、实验运行结果: 输入进程数,及相关资源数量分配 选择算法完成的操作:1 查看进程情况 请求分配 2.1分配失败 2.2 分配成功 2.3 继续分配失败,退出 九、实验心得: 通过此次实验,我能够更加深入的理解银行家算法的执行过程,也懂得用银行家算法去防止发生死锁,有效地解决了资源利用率低的问题,保证了系统的安全性。 当然在实验过程中,我也遇到了一些困难,但是我通过及时请教同学,查询相关资料,及时解决了问题,但仍有不足之处,我将会在今后学习中更加努力。 附录:源代码(部分) #include class Source //资源的基本构成以及功能 { private: public: int R1;//定义三类类资源 int R2;int R3; Source(int r1 = 0,int r2 = 0,int r3 = 0){ R1=r1;R2=r2;R3=r3;} Source(Source& s){ R1=s.R1;R2=s.R2;R3=s.R3;} void setSource(int r1 = 0,int r2 = 0,int r3 = 0)//设置各类资源 { R1=r1;R2=r2;R3=r3;} void add(Source s)//加法 { R1=R1+s.R1;R2=R2+s.R2;R3=R3+s.R3;} void sub(Source s)//减法 { R1=R1-s.R1;R2=R2-s.R2;R3=R3-s.R3;} bool lower(Source s)//比较 { if(R1 > s.R1)return false; if(R2 > s.R2)return false; if(R3 > s.R3)return false; return true;} }; struct Process//进程属性构成 { Source claim;//进程最大需求量 Source allocation;//进程占有量 Source claim_allocation;//进程需求量 Source currentAvail;//进程可获得量 }; class Data//封装所有数据 { public: Process *p;//进程指针 Source sum;//总资源量 Source available;//可获得量 Source ask;//请求量 int pLength;//进程个数 int * ruler;//逻辑尺 void clearProcess()//进程currentAvail清零 { for(int i=0;i { p[i].currentAvail.setSource(0, 0, 0);} } }; class DataInit//封装初始化数据函数类 { private: public: DataInit()//构造函数 { } void initLength(Data *db)//设置进程个数 { int n; cout<<“输入进程数: ”; cin>>n; db->pLength = n; db->p = new Process[n]; if(!db->p) {cout<<“error!no enough memory space!”;return;} db->ruler = new int[n]; if(!db->ruler){cout<<“error!no enough memory space!”;return;} } void setAsk(Data *db)//设置请求资源量 { int r1,r2,r3;r1=0;r2=0;r3=0; db->ask.setSource(r1,r2,r3);} void initSum(Data *db)//设置总资源量 { int r1,r2,r3;cout<<“Available(R1,R2,R3): ”;cin>>r1>>r2>>r3;db->sum.setSource(r1,r2,r3);} void initAvail(Data *db)//设置可获得量 { int r1,r2,r3;cout<<“输入初始分配 Allocation:n”;cout<<“available[R1,R2,R3]:n ”; cin>>r1>>r2>>r3; db->available.setSource(r1,r2,r3);} void initProcess(Data *db)//设置各进程属性值 { int r1,r2,r3;cout<<“输入t0时分配 Allocation:n”;for(int i=0;i cout<<'p'< cin>>r1>>r2>>r3; db->p[i].allocation.setSource(r1,r2,r3); cout<<'p'< cin>>r1>>r2>>r3; db->p[i].claim.setSource(r1,r2,r3); r1=db->p[i].claim.R1-db->p[i].claim.R1;//设置进程p[i] 的 claim_allocation r2=db->p[i].claim.R2-db->p[i].claim.R2; r3=db->p[i].claim.R3-db->p[i].claim.R3; db->p[i].claim_allocation.setSource(r1, r2, r3); } } }; class Display//封装显示方法 { private: public: Display()//构造函数 { } void displaySource(Source s)//设置基本资源显示方式 {cout< displayAvailable(Source s)//显示可获得资源量 {displaySource(s);} void displayProcess(Process *p,int length)//显示进程基本信息 { for(int i=0;i { cout<<“ p”< displaySource(p[i].claim); cout<<“tt”; displaySource(p[i].allocation); cout< } cout< void displaySafeList(Data *db)//显示安全序列 { for(int i=0;i { cout<<“ p”< ”; displaySource(db->p[db->ruler[i]].currentAvail); cout<<“ ”; displaySource(db->p[db->ruler[i]].claim); cout<<“ ”; displaySource(db->p[db->ruler[i]].allocation); cout<<“ ”; displaySource(db->p[db->ruler[i]].claim_allocation); cout<<“ true”; cout< } } void displayAskResult(Data *db,int n)//显示请求资源结果 { if(n==0) {cout<<“不分配,请求量大于当前可获得量!n”;return;} if(n==1) {cout<<“不分配,请求量大于当前可获得量!n”;return;} if(n==2) {cout<<“不分配,找不到安全序列!n”;return;} if(n==3) { cout<<“存在安全序列:”; for(int i=0;i {cout< cout< char c='N'; cout<<“查看安全序列详情?(Y/N)”; cin>>c; if(c=='Y'||c=='y') { cout<<“ 进程 currentavail claim allocation claim-allocation possiblen”; displaySafeList(db); } return; } } }; class FindSafeList//寻找安全序列 { private: public: FindSafeList()//构造函数 {} bool checkList(Data *db)//检查一个序列安全性 { int i=0;//i用于循环 db->p[db->ruler[i]].currentAvail.add(db->available);//将当前系统可用资源量 赋给该序列的第一个进程 if(!db->p[db->ruler[i]].claim_allocation.lower(db->p[db->ruler[i]].currentAvail))//若当前进程currentAvail小于该进程需求量(claim-allocation),返回false {return false;} for(i=1;i< db->pLength;i++) { //当前进程的可获得资源量currentAvail获得前一个进程的未释放资源前可获得资源量currentAvail db->p[db->ruler[i]].currentAvail.add(db->p[db->ruler[i-1]].currentAvail); //当前进程的可获得资源量currentAvail获得前一个进程的释放的资源量 db->p[db->ruler[i]].currentAvail.add(db->p[db->ruler[i-1]].allocation); //若当前进程currentAvail小于该进程需求量(claim-allocation),返回false if(!db->p[db->ruler[i]].claim_allocation.lower(db->p[db->ruler[i]].currentAvail)) { return false;} //若当前进程currentAvail大于该进程总资源量,返回false if(!db->p[db->ruler[i]].currentAvail.lower(db->sum)) { return false;} } return true;//该序列进程安全。返回true } bool exsitSafeList(Data *db)//判断是否存在安全序列 { int i = 0; for(i = 0;i < db->pLength;i++)//设置逻辑尺的刻度值 { db->ruler[i] = i;} while(1) //该循环将检测逻辑尺刻度值的全排列 { if(checkList(db)) //找到一个安全序列,返回true { return true;} db->clearProcess();//将所有进程的currentAvail清零 if(!next_permutation(db->ruler,db->ruler+db->pLength)) //所有排列完毕后退出生成排列库函数的调用 { return false; } } return false;} int findSafeList(Data *db, int i=0)//寻找安全序列 { //请求值大于系统当前可用资源值,返回0 if(!db->ask.lower(db->available)) { return 0;} //请求值大于当前进程需求资源值,返回1 if(!db->ask.lower(db->p[i].claim_allocation)) { return 1;} Source s(db->p[i].allocation);//根据请求,分配资源值 db->available.sub(db->ask); db->p[i].allocation.add(db->ask); db->p[i].claim_allocation.sub(db->ask); if(!exsitSafeList(db))//判断是否存在安全序列 { db->available.add(db->ask); //不存在安全序列,回滚,恢复分配前状态,并返回2 db->p[i].allocation.sub(db->ask); db->p[i].claim_allocation.add(db->ask); return 2; } db->ask.setSource(0,0,0);//找到安全序列,将请求资源置零,返回3 return 3;} }; void main(){ Data *db;db=new Data;if(!db){ cout<<“error!no enough memory space!”;return;} DataInit dataInit;dataInit.initLength(db);//设置进程个数 dataInit.initSum(db);//设置系统总资源量 dataInit.initAvail(db);//设置当前系统可获得资源量 dataInit.initProcess(db);//设置t0时刻进程基本状态 Display display;FindSafeList findSafeList;int r1=0,r2=0,r3=0;int c;db->ask.setSource(r1,r2,r3);//设置请求资源为0,即无请求 c=findSafeList.findSafeList(db,0);//寻找安全序列,返回结果 if(c!=3){ cout<<“t0时刻的进程组不存在安全序列!n”;return;} int choice=1;int pi; while(choice){ cout<<“n 选择操作:n 查看进程情况n 请求分配资源n 0 退出n ”; cin>>choice;switch(choice){ case 1: { } case 2: { } case 0: { default: { } } } cout<<“当前资源量available[R1,R2,R3]:n ”;display.displayAvailable(db->available);cout<第二篇:排序算法总结
第三篇:用php实现的各种排序算法总结
第四篇:51CTO下载-排序和算法总结
第五篇:操作系统课程设计实验报告-用C++实现银行家算法