您的位置:首页 > 其它

冒泡插入选择排序解析以及信号量机制

2013-02-26 22:39 288 查看
由于待会儿要出去吃饭,所以就不啰嗦其他东东咯,直奔主题吧!今天简单解析下三种排序算法,目的是要清楚永久滴记住它,另外讲下操作系统中的信号量机制。冒泡算法:顾名思义,就是像水底冒泡泡一样,最上面那个泡泡是最大的,最下面的泡泡是最小的,当然,实际应用中大小因人而异。我们都知道,每个算法都有其核心的东西,也就是核心方法,而对于冒泡排序来说,无非是那个两重循环,那怎么来理解这个两重循环呢?循环肯定是for啊for的啦,那怎么for呢?对于冒泡来说,第一层for我们可以这样子理解,就是需要确定几个泡泡的位置,这样子说吧,有一堆大小不一的泡泡,怎么实现大泡在上,小泡在下,那么确定一个泡泡的位置需要一次操作,那么如果有N个泡泡,需要几次操作呢?很容易理解,需要N-1次操作,之所以减一是因为,确定了第 N-1个泡泡之后,第N个就自然而然确定了,所以第N个是不用操作的。接下来是第二个for了,第二个for是确定需要交换的次数,直接算,我们无法很快得知,那么用一种迂回,如果已经确定了第2个泡泡,那么就只需要对其他泡泡进行N-2个泡泡进行交换排序了,即这个for中关联到第一个for的操作数,而对于交换,我们看成从上到下,那么始终都包含有第一个数和第二个数的交换,直到需要交换的下标达到最后已经确定位置的下标为止。下面送上核心部分代码:(时间复杂度为O(n2))—–java编程网:http://www.haohaoblog.com
For(int i=0;i<length-1;i++){For(int k=length-1;k>i;k–)//k=length-1意思是下标最大的那个数,即最后一个数{If(arr[k]>a[k-1]){Temp=arr[k];Arr[k]=arr[k-1];Arr[k-1]=temp;}}}
插入排序:对于插入排序来说,首先明白原理是一堆数,一个个来加入到新的数组中,即这个新数组原本是没有一个数,从一堆数中取一个数放进去,然后再取一个数,比较,大的放后面,小的放前面,以此类推,这种排序消耗的是来一个数和新数组的所有数进行比较,可以知道其时间复杂度是O(n2)。插入排序的核心地方在于如何与新数组中的数进行比较,然后交换位置,首先,这个算法包含两个循环,一个是for,一个是while,第一个for表达的是插入的数的个数,while是为了判断,前一堆数和新插入的数的大小比较,下面看核心部分的代码,然后来做阐述:—-java编程网:http://www.haohaoblog.com
For(int i=1;i<n;i++)//因为是要和第一个数相比,所以,取值取第二个,即下标为1{Temp=arr[i];k=i;While(k>=1;arr[k-1]>temp)//因为每次都是比较到第一个为止,{Arr[k]=arr[k-1];//如果每次都是前一个比后一个大,那么比较到最后,总是后替换前k–;}//假设没有这个while,那么接下里的交换就是自己和自己交换了。Arr[k]=temp;//插入到最适合的那个数的后面}
接下来,再说下选择排序,所谓选择排序,就是从一堆数中选择一个最小的,然后增加到一个新的数组中,这个新的数组最开始是空的。那么可想而知,从一堆数中找到最小的数,成为这个算法的核心部分,这个有点像冒泡,但是又不是冒泡,冒泡是涉及到了很多交换,而选择只是用一个游标来标记最小数的下标,确定这个游标所指的数值,然后和新数组的接下来的一个位置进行交换,这样就可以确定新数组中的每个数都比旧数组中的数要小。这样的话,不难看出,需要两个for循环,外层的for表示需要确定位置的个数,比如三个数进行选择排序,那么需要确定两个位置就行了,第三个就明显得出,第二个for是用来取出旧数组最小的数。于是,很容易得到以下核心部分的代码:
For(int i=0;i<length-1;i++){Minindex=I;//设置一个游标,这个游标来指示最小数For(int k=i+1;k<length;k++){If(arr[minindex]>arr[k])//和最小数进行比较,不断更新,最终获得旧数组中的最小数{Minindex=k;}}If(minindex!=i)//将最小数和新数组中接下来的一个位置的数进行交换{Temp=arr[i];Arr[i]=arr[minindex];Arr[minindex]=temp;}}
写完这个算法,想说明下选择排序是不稳定,为什么不稳定呢?比如利用选择排序来排列下面的一组数:3,2,5,3,1,很容易知道,第一次的过后,第一个3会和1交换,那么这样,第一个3和第二3的位置就变换了,后来也不会再变回来了,所以,对于两个相等的数,在排序中前后位置如果在排序过后会改变,那么这种排序就是不稳定的。最后说下,信号量机制的问题,这是一个操作系统的范畴,为的是解决死锁问题。对于这个问题,做如下分析:信号量的概念首先由E.W.Dijkstra在1965年提出的。semaphore(信号量)是一个大于等于零的整型变量。 对信号量有两个原子操作:-和+,DOWN()和UP(),SLEEP()和WAKEUP(),P()和V(),Wait() 和Signal()。虽然它们名字都不一样,可意思都是相同的,拿down和up操作来说明。
DOWN(t)操作
递减信号量t的值:先检查t是否大于0,若t大于0则t减去1;若t的值为0,则进程进入睡眠状态,而此时的DOWN操作并没有结束。这是原子操作,用来保证一旦一个信号量的操作开始,则在操作完成或阻塞之前别的进程不允许访问该信号量。
UP(t)操作
递增信号量t的值:如果一个或多个进程在该信号量t上睡眠,则无法完成一个先前的DOWN操作,则由系统选择其中的一个并允许起完成它的DOWN操作。因此,对一个有进程在起上睡眠的信号量执行一次UP操作以后,该信号量的值仍旧是0,但在其上睡眠的进程却少了一个。递增信号量的值和唤醒一个进程的操作也是原子操作,不可分割的。这样做保证不会有进程因为执行UP操作而被阻塞。
下面是一个使用信号量的生产者和消费者的实例。
#define N 100 /*number of slots in the buffer*/
typedef int semaphore;
semaphore mutex=1;
semaphore empty=N;
semaphore full=0;
void producer(void){
int item;
while(TRUE){
produce_item(&item);
down(&empty);
down(&mutex);
enter_item(item);
up(&mutex);
up(&full);
}
}
void consumer(void){
int item;
while(TRUE){
down(&full);
down(&mutex);
remove_item(&item);
up(&mutex);
up(&empty);
consume_item(item);
}
}
多线程程序中最简单也是最常用的同步机制要算是mutex(互斥量)了。一个mutex也就是一个信号量,它只提供两个基本操作:DOWN和UP。一旦某个线程调用了DOWN,其它线程再调用DOWN时就会被阻塞。当这个线程调用UP后,刚才阻塞在DOWN里的线程中,会有一个且仅有一个被唤醒。换句话说,对于一个给定的mutex,只有一个线程可以在DOWN和UP调用之间获取处理器时间。这里的mutex 用来保护访问的临界区,避免数据出现竞争条件。
在这个实例中,开始便定义了一个大小为100的缓冲区,然后定义整型的信号量并初始化。empty是指剩余缓冲区的容量,而full则与之相对,已占用缓冲区的大小。首先是生产者函数,produce_item(&item);生产一个产品,这时候并没有对缓冲区进行操作,接着对empty和 mutex分别DOWN操作,(注意:down(&empty)和down(&mutex)顺序不能颠倒。如果颠倒了这两个操作,会出现生产者进入睡眠状态却占用缓冲区导致消费者不能访问缓冲区的情况。)enter_item(item)指生产者把产品放入缓冲区,对mutex执行UP操作是撤消对缓冲区的保护。同理分析消费者函数。在这里有一个疑问,对于消费者和生产者来说,最后一个的up,是先up互斥量mutex,还是up其他信号量,如full或empty,还是这两种都可以,感觉上两者都可以,因为不涉及到操作。如果有不同答案的,欢饮骚扰呀!由于待会儿要出去吃饭,所以就不啰嗦其他东东咯,直奔主题吧!今天简单解析下三种排序算法,目的是要清楚永久滴记住它,另外讲下操作系统中的信号量机制。冒泡算法:顾名思义,就是像水底冒泡泡一样,最上面那个泡泡是最大的,最下面的泡泡是最小的,当然,实际应用中大小因人而异。我们都知道,每个算法都有其核心的东西,也就是核心方法,而对于冒泡排序来说,无非是那个两重循环,那怎么来理解这个两重循环呢?循环肯定是for啊for的啦,那怎么for呢?对于冒泡来说,第一层for我们可以这样子理解,就是需要确定几个泡泡的位置,这样子说吧,有一堆大小不一的泡泡,怎么实现大泡在上,小泡在下,那么确定一个泡泡的位置需要一次操作,那么如果有N个泡泡,需要几次操作呢?很容易理解,需要N-1次操作,之所以减一是因为,确定了第 N-1个泡泡之后,第N个就自然而然确定了,所以第N个是不用操作的。接下来是第二个for了,第二个for是确定需要交换的次数,直接算,我们无法很快得知,那么用一种迂回,如果已经确定了第2个泡泡,那么就只需要对其他泡泡进行N-2个泡泡进行交换排序了,即这个for中关联到第一个for的操作数,而对于交换,我们看成从上到下,那么始终都包含有第一个数和第二个数的交换,直到需要交换的下标达到最后已经确定位置的下标为止。下面送上核心部分代码:(时间复杂度为O(n2))—–java编程网:http://www.haohaoblog.comFor(int i=0;ii;k–)//k=length-1意思是下标最大的那个数,即最后一个数{If(arr[k]>a[k-1]){Temp=arr[k];Arr[k]=arr[k-1];Arr[k-1]=temp;}}}插入排序:对于插入排序来说,首先明白原理是一堆数,一个个来加入到新的数组中,即这个新数组原本是没有一个数,从一堆数中取一个数放进去,然后再取一个数,比较,大的放后面,小的放前面,以此类推,这种排序消耗的是来一个数和新数组的所有数进行比较,可以知道其时间复杂度是O(n2)。插入排序的核心地方在于如何与新数组中的数进行比较,然后交换位置,首先,这个算法包含两个循环,一个是for,一个是while,第一个for表达的是插入的数的个数,while是为了判断,前一堆数和新插入的数的大小比较,下面看核心部分的代码,然后来做阐述:—-java编程网:http://www.haohaoblog.comFor(int i=1;i=1;arr[k-1]>temp)//因为每次都是比较到第一个为止,{Arr[k]=arr[k-1];//如果每次都是前一个比后一个大,那么比较到最后,总是后替换前k–;}//假设没有这个while,那么接下里的交换就是自己和自己交换了。Arr[k]=temp;//插入到最适合的那个数的后面}接下来,再说下选择排序,所谓选择排序,就是从一堆数中选择一个最小的,然后增加到一个新的数组中,这个新的数组最开始是空的。那么可想而知,从一堆数中找到最小的数,成为这个算法的核心部分,这个有点像冒泡,但是又不是冒泡,冒泡是涉及到了很多交换,而选择只是用一个游标来标记最小数的下标,确定这个游标所指的数值,然后和新数组的接下来的一个位置进行交换,这样就可以确定新数组中的每个数都比旧数组中的数要小。这样的话,不难看出,需要两个for循环,外层的for表示需要确定位置的个数,比如三个数进行选择排序,那么需要确定两个位置就行了,第三个就明显得出,第二个for是用来取出旧数组最小的数。于是,很容易得到以下核心部分的代码:For(int i=0;i<length-1;i++){Minindex=I;//设置一个游标,这个游标来指示最小数For(int k=i+1;karr[k])//和最小数进行比较,不断更新,最终获得旧数组中的最小数{Minindex=k;}}If(minindex!=i)//将最小数和新数组中接下来的一个位置的数进行交换{Temp=arr[i];Arr[i]=arr[minindex];Arr[minindex]=temp;}}写完这个算法,想说明下选择排序是不稳定,为什么不稳定呢?比如利用选择排序来排列下面的一组数:3,2,5,3,1,很容易知道,第一次的过后,第一个3会和1交换,那么这样,第一个3和第二3的位置就变换了,后来也不会再变回来了,所以,对于两个相等的数,在排序中前后位置如果在排序过后会改变,那么这种排序就是不稳定的。最后说下,信号量机制的问题,这是一个操作系统的范畴,为的是解决死锁问题。对于这个问题,做如下分析:信号量的概念首先由E.W.Dijkstra在1965年提出的。semaphore(信号量)是一个大于等于零的整型变量。 对信号量有两个原子操作:-和+,DOWN()和UP(),SLEEP()和WAKEUP(),P()和V(),Wait() 和Signal()。虽然它们名字都不一样,可意思都是相同的,拿down和up操作来说明。DOWN(t)操作递减信号量t的值:先检查t是否大于0,若t大于0则t减去1;若t的值为0,则进程进入睡眠状态,而此时的DOWN操作并没有结束。这是原子操作,用来保证一旦一个信号量的操作开始,则在操作完成或阻塞之前别的进程不允许访问该信号量。UP(t)操作递增信号量t的值:如果一个或多个进程在该信号量t上睡眠,则无法完成一个先前的DOWN操作,则由系统选择其中的一个并允许起完成它的DOWN操作。因此,对一个有进程在起上睡眠的信号量执行一次UP操作以后,该信号量的值仍旧是0,但在其上睡眠的进程却少了一个。递增信号量的值和唤醒一个进程的操作也是原子操作,不可分割的。这样做保证不会有进程因为执行UP操作而被阻塞。下面是一个使用信号量的生产者和消费者的实例。#define N 100 /*number of slots in the buffer*/typedef int semaphore;semaphore mutex=1;semaphore empty=N;semaphore full=0;void producer(void){int item;while(TRUE){produce_item(&item);down(&empty);down(&mutex);enter_item(item);up(&mutex);up(&full);}}void consumer(void){int item;while(TRUE){down(&full);down(&mutex);remove_item(&item);up(&mutex);up(&empty);consume_item(item);}}多线程程序中最简单也是最常用的同步机制要算是mutex(互斥量)了。一个mutex也就是一个信号量,它只提供两个基本操作:DOWN和UP。一旦某个线程调用了DOWN,其它线程再调用DOWN时就会被阻塞。当这个线程调用UP后,刚才阻塞在DOWN里的线程中,会有一个且仅有一个被唤醒。换句话说,对于一个给定的mutex,只有一个线程可以在DOWN和UP调用之间获取处理器时间。这里的mutex 用来保护访问的临界区,避免数据出现竞争条件。在这个实例中,开始便定义了一个大小为100的缓冲区,然后定义整型的信号量并初始化。empty是指剩余缓冲区的容量,而full则与之相对,已占用缓冲区的大小。首先是生产者函数,produce_item(&item);生产一个产品,这时候并没有对缓冲区进行操作,接着对empty和 mutex分别DOWN操作,(注意:down(&empty)和down(&mutex)顺序不能颠倒。如果颠倒了这两个操作,会出现生产者进入睡眠状态却占用缓冲区导致消费者不能访问缓冲区的情况。)enter_item(item)指生产者把产品放入缓冲区,对mutex执行UP操作是撤消对缓冲区的保护。同理分析消费者函数。在这里有一个疑问,对于消费者和生产者来说,最后一个的up,是先up互斥量mutex,还是up其他信号量,如full或empty,还是这两种都可以,感觉上两者都可以,因为不涉及到操作。如果有不同答案的,欢饮骚扰呀!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: