1、冒泡排序
最初在学c语言时,老师就教的这个排序算法,原理比较简单:从数组下标为0处开始遍历,相邻之间进行比较,若a[i]>a[i+1],则exchange(a[i],a[i+1]),当然也可以将小的往后传递,将此过程不断进行,那么最后数组就有序了。
要点:(1)每遍历一遍,末尾就得到一个最大值(或最小值),那么接下来的遍历是不是每次都减少一个元素就好了,因为后边的已经排好序了啊。
(2)遍历n-1遍就排好序了,因为最后一遍只剩一个元素了,它一定放那儿,所以最后一遍就不用遍历了。
当然如果数据小,又懒得优化,多进行几遍也一样可以排序的,比如这样:
for(int i=0;i<n;i++){ //遍历了n遍
for(int j=0;j<n-1;j++){ //每次比较当前元素和下一个元素,所以循环结束条件为n-1
{
if(a[j]>a[j+1])
{ int t=a[j];
a[j]=a[j+1];
a[j+1]=t; }
}
}
那么标准的冒泡排序,就是算法复杂度为n*(n-i)次也就是n^2如下:
for(int i=0;i<a.length-1;i++){ //遍历n-1遍就够了
for(int j=0;j<a.length-i-1;j++){ //每次比较当前元素和下一个元素,每遍历一遍少比较一个元素,所以循环结束条件为n-i-1
{
if(a[j]>a[j+1])
{ int t=a[j];
a[j]=a[j+1];
a[j+1]=t; }
}
}
2、选择排序
这个也是学c语言的时候和冒泡一起学的,它和冒泡算法复杂度一样,原理为:从数组下标为0处开始遍历,每次取出剩下元素的最大值(或最小值)放在当前位置,也就是说,第一次取最大的放第一个位置,第二次取次大的放第二个位置....执行n-1次就好了,最后一个只能放最后了。
标准选择排序代码如下:
for(int i=0;i<a.length-1;i++){ //遍历n-1遍就够了
int k=i; //k为剩下元素中的最大值下标,初始化为当前位置
for(int j=i+1;j<a.length-1;j++){ //找出剩下元素中的最大值下标
if(a[j]>a[k])
k=j;
}
if(k!=i){ //若最大值不是当前位置的值,则交换,每次将最大值放前边
int t=a[k];
a[k]=a[i];
a[i]=t;
}
}
3、插入排序
适用于较少元素时,效率比较高,原理:从数组下标为0处开始遍历,每次保证已经遍历的元素为有序序列,即每次将要遍历的数插入到前边数列的合适位置,保证已经遍历的元素为有序数列。如:遍历第一个元素,因为目前前边只有这一个元素,所以它是有序的,遍历第二个元素,和第一个元素比较,看它插在他前边还是后边,这样就保证已经遍历的元素都有序了,之后的类似,和前边的比较,然后插入合适的位置。
插入排序代码如下:
for(int i=1;i<a.length-1;i++){ //从下标1开始,一个元素的时候一定有序啊
int j=i-1; // j初始值为前一个元素,因为要将前边的元素一一和当前元素比较
int k=a[i]; //记录当前位置元素的值,因为如果后边要移动的话会覆盖的
while(j>0&&a[j]>k){ //将大于当前元素值的都依次向前移一位,好空出适合的位置给当前元素值
a[j+1]=a[j];
j--;
}
a[j+1]=k; //因为j处元素值小于等于k (j处跳出循环的),所以k的适合位置为j+1
}
4、堆排序
要想理解堆排序,首先你要知道最大堆,要想理解最大堆,你得知道二叉树。
二叉树:每个节点最多有俩个孩子节点。
最大堆:父亲节点的值总是大于孩子节点的值。
当然在这里二叉树的存储结构不是链表,是使用数组存的:(1)数组下标为0处是根节点。
(2)父亲节点下标*2为左孩子下标,父亲节点下标*2+1为右孩子下标。
根据这俩条准则我们就可以将二叉树存在数组了。
堆排序原理:我们知道最大堆的性质(父亲节点的值总是大于孩子节点的值),那么根节点处不就是当前数列的最大值吗,那么我们每次取根节点的值放在末尾,然后将最大堆的大小-1,更新最大堆,取根节点放后边.....不断执行这个过程,直到最大堆中只剩一个元素,此时数组就是一个有序数组了。
根据原理可以看出我们需要编的操作有(1)建最大堆 (2)更新最大堆,其实建立最大堆就是不断更新最大堆的过程,如果我们将每个结点都执行一遍更新最大堆操作(即父亲节点的值总是大于孩子节点的值,不符合的话将父亲节点与最大的孩子交换位置),当然执行顺序必须是从下往上,然后只需从非叶子节点开始执行就好了(非叶子节点就是有孩子的结点)。
堆排序代码如下:
//更新最大堆操作
void dfDui(int x) { //参数为父亲节点下标
int lchild=x*2; //左孩子下标
int rchild=x*2+1; //右孩子下标
int max=x; //最大值下标初始为父亲下标
if(lchild<size&&a[lchild]>a[max]) //比较找出最大值
max=lchild;
if(rchild<size&&a[rchild]>a[max])
max=rchild;
if(max!=x){ //若父亲节点为最大值,则符合性质,否则交换,将最大值移到父亲节点处,然后因为孩子节点处已改变,更新此节点。
int t=a[max];
a[max]=a[x];
a[x]=t;
dfDui(max);
}
}
//建最大堆操作
void creatDui(){
for(int i=a.length/2+1;i>=0;i--){ //叶子结点数为结点总数一半且都在最后(可以从孩子节点下标的算法为父亲节点*2看出),因此 duDui(i); // a.length/2+1处开始为非叶子节点
}
}
//堆排序操作
void sort(){
creatDui(); //建最大堆
for(int i=size-1;i>=1;i--){ //每次将第一个数与最后一个数交换,然后大小-1,更新已经改变的根节点
int t=a[0];
a[0]=a[size-1];
a[size-1]=t;
size--;
dfDui(0);
}
}
5、快速排序
快速排序是实际运用中用的最多的算法,虽然它在最坏的情况下会达到n^2,但它的平均性能非常好,期望时间复杂度为nlgn,而且隐含的常数因子非常小,并且是原址排序。
快速排序原理:从一组数中任意选出一个数,将大于它的数放右边,小于它的数放左边,然后再从左边和右边的俩组数中分别执行此操作,知道组中元素数为1,此时,数组就是有序的了。
从原理中可以清楚的看出此操作为递归操作,其代码如下:
int partsort(int a[],int l,int r){ //将比a[r]小的元素放左边,比它大的放右边,最后把a[r]放中间
int i=l; //i为比a[r]大的元素的下标,初始为开始位置l
for(int j=l;j<r;j++){
if(a[j]<=a[r]){ //如果元素比a[r]小则和大的元素交换位置,目的让小的放一起,大的放一起
int t=a[j]; //可以自己手运行几遍这个循环
a[j]=a[i];
a[i]=t;
i++;
}
}
int t=a[i]; //a[i]为小元素和大元素的交界处,将a[r]与之交换
a[i]=a[r];
a[r]=t;
return i; //返回交界处下标,继续排前边的和后边的这俩组
}
void quicksort(int a[],int l,int r){
if(l<r){ //递归结束条件为组中只剩一个元素
int p=partsort(a,l,r); //分成俩组返回交界处
quicksort(a,l,p-1); //继续分左边
quicksort(a,p+1,r);
}
}
主函数中调用代码为,若数组大小为n,则:quicksort(a,0,n-1);
待续待续哈..........