快速排序、归并排序、堆排序
快速排序:时间上不稳定,空间O(1);
归并排序:时间稳定在nlogn,空间浪费大
堆排序:时间稳定在nlogn,空间O(1);
3.快速排序
24,26,31,78,88,99,32,-2,-1,1,57,11,13,15,18,21,35,59,62,65,68,69,71,73,75,3,5,8,38,45,48,49,52,55
优化版本从每组数里面的前三个选择中间的数作为基准数,未优化的就选取比较后分开的每组数的第一个数作为基准数就可以.
第一轮 排好一个数
3,5,8,-2,-1,1,11,13,15,18,21,24,#26#,31,78,88,99,32,57,35,39,62,65,68,69,71,73,75,38,45,48,49,52,55 O(n)
第二轮 排好两个数
-2,-1,1,3,#5#,8,11,13,15,18,21,24 ,#26#,31,32,57,35,39,62,65,68,69,71,73,75,38,45,48,49,52,55,#78#,88,99
第二轮 排好四个数
-2,3#-1#,1,3,#5#,8,#11#,13,15,18,21,24 ,#26#,31,#32#,57,35,39,62,65,68,69,71,73,75,38,45,48,49,52,55,#78#,#88#,99
需要进行k轮比较
20+21+22+…+2(k-1)=2^k-1=n/2–>logn
每次比较O(0)
nlogn
具体实现:
24,26,31,78,88,99,32,-2,-1,1,57,11,13,15,18,21,35,59,62,65,68,69,71,73,75,3,5,8,38,45,48,49,52,55
1>26比24大,让26和最末尾的55交换位置
2>55比24大,让55和倒数第二的52交换位置
。。。。。
遇到比24小的和24交换位置。
//快速排序的第一次操作,选择每组第一个数为基准数
#include <stdio.h>
void show(int* arr,int size)
{
int i;
for(i=0;i<size;i++)
printf("%d ",arr[i]);
}
void m1(int* arr,int size)
{
int left=1;int right=size-1;//基准右边一个数作为左边界,未交换过的最末尾的数是右边界
int i;
for(i=0;i<size-1;i++)//一共有n个数操作n-1次;
{
if(arr[left-1]<arr[left])//如果基准右边一个数(左边界)比基准大的话,将它和右边界交换位置,右边界左移
{
int w=arr[left];
arr[left]=arr[right];
arr[right]=w;
right--;
}
else//如果左边界比基准小的话,让左边界和基准交换位置,左边界右移
{
int w=arr[left-1];
arr[left-1]=arr[left];
arr[left]=w;
left++;
}
}
}
int main()
{
int arr[]={24,26,31,78,88,99,32,-2,-1,1,57,11,13,15,18,21,35,59,62,65,68,69,71,73,75,3,5,8,38,45,48,49,52,55};
int length=sizeof(arr)/4;
m1(arr,length);
show(arr,length);
return 0;
}
//递归实现快速排序的完整代码
#include <stdio.h>
void show(int* arr,int size)
{
int i;
for(i=0;i<size;i++)
printf("%d ",arr[i]);
}
void m1(int* arr,int left1,int right1)
{
int left=left1+1;int right=right1;
int i;
while(left<=right)//和n-1次等价,反正运行n-1次也是让左边界刚好超过右边界
{
if(arr[left-1]<arr[left])
{
int w=arr[left];
arr[left]=arr[right];
arr[right]=w;
right--;
}
else
{
int w=arr[left-1];
arr[left-1]=arr[left];
arr[left]=w;
left++;
}
}
if(right-1>left1)//递归的终止条件
m1(arr,left1,right-1);
if(right1>left)
m1(arr,left,right1);
/*if(right-1-left1>=1)//递归的终止条件
m1(arr,left1,right-1);
if(right1-left>=1)
m1(arr,left,right1);*/
}
int main()
{
int arr[]={24,26,31,78,88,99,32,-2,-1,1,57,11,13,15,18,21,35,59,62,65,68,69,71,73,75,3,5,8,38,45,48,49,52,55};
int length=sizeof(arr)/4;
m1(arr,0,length-1);
show(arr,length);
return 0;
}
写完感觉递归是一个比较难想明白的事情,而且在这个算法中个人感觉很多条件可以换个写法,比如标注出来的终止条件。而且游标的叫法有点多,换了一个写法并没有明显错误。2min写完。
4.归并排序
24,26,31,78,88,99,32,-2,-1,1,57,11,13,15,18,21,35,59,62,65,68,69,71,73,75,3,5,8,38,45,48,49,52,55
首先要了解一个合并有序序列
1,3,6,8,11,14,18,22,25,31
2,4,7,9,13,17,24
比较头部,每次选两个头部最小的,O(n)的时间复杂度就将其合并成一个有序序列了。
//归并有序序列的数组方法
#include <stdio.h>
void show(int* arr,int size)
{
int i;
for(i=0;i<size;i++)
printf("%d ",arr[i]);
}
void m1(int *arr,int *brr,int *crr,int size1,int size2)
{
int i=0,j=0;
int index=0;
while(1)
{
if(arr[i]<brr[j])
{
crr[index]=arr[i];
i++;
if(i>size1)//如果arr先遍历完
break;
}
else
{
crr[index]=brr[j];
j++;
if(j>size2)//如果brr先遍历完
break;
}
index++;
}
for(;i<size1;i++)//如果arr还有剩余
{
crr[index]=arr[i];
index++;
}
for(;j<size2;j++)//如果brr还有剩余
{
crr[index]=arr[j];
index++;
}
}
int main()
{
int arr[]={1,3,6,8,11,14,18,22,25,31};
int brr[]={2,4,7,9,13,17,24};
int crr[17];
int length1=sizeof(arr)/4;
int length2=sizeof(brr)/4;
m1(arr,brr,crr,length1,length2);
show(crr,17);
return 0;
}
归并排序:
24,26,31,78,88,99,32,-2,-1,1,57,11,13,15,18,21,35,59,62,65,68 n个有序数组,每个数组长度为1
两两合并有序序列
24,26 31,38 88,99 -2,32 -1,1 11,57 11,13 15,18 21,35 59,62 65,68
n/2个有序数组 O(n)
四四合并
24,26,31,38 -2,32,88,99 -1,1,11,97 11,13,15,18 21,35,59,62 65,68
n/4个有序数组 O(n)
。。。。。。。
经过约logn轮,得到一个有序序列。
所以时间复杂度是 nlogn
每次从序列中取出两个数组,排列后放在一个新序列里然后拷贝回原序列
//归并有序序列的递归方法
#include <stdio.h>
#include <stdlib.h>
void show(int* arr,int size)
{
int i;
for(i=0;i<size;i++)
printf("%d ",arr[i]);
}
void m1(int *arr,int left,int right,int *arr2)
{
if(left>=right)
return; //终止条件
int mid=(left+right)/2;//将数组分为两个数组分别进行递归计算
m1(arr,left,mid,arr2);
m1(arr,mid+1,right,arr2);//递归算完之后得到了两个有序数组。
int left1=left;
int right1=mid;
int left2=mid+1;
int right2=right;
int index=left;
//下面是对两个有序数组的排序
while(left1<=right1&&left2<=right2)
{
if(arr[left1]<arr[left2])
{
arr2[index]=arr[left1];
index++;
left1++;
}
else
{
arr2[index]=arr[left2];
index++;
left2++;
}
}
while(left1<=right1)//如果前一半数组有剩余
{
arr2[index]=arr[left1];
index++;left1++;
}
while(left2<=right2)//如果后一半数组有剩余,这里并不能简化,因为不知道哪个数组剩下的数多!
{
arr2[index]=arr[left2];
index++;left2++;
}
int i;
for(i=0;i<=right;i++)
{
arr[i]=arr2[i];
}
}
int main()
{
int arr[]={33,8,11,2,99,45,41,0,6,76,63,12,22,55,77,-12,-33,44,8,24,51,33,68,5};
int length=sizeof(arr)/4;
int* arr2=(int*)malloc(sizeof(arr));//申请一个和arr大小一样的空间用来排序
m1(arr,0,length-1,arr2);
show(arr,length);
return 0;
}
二叉树
完全二叉树的特性:
1>完全二叉树用数组来存储
2>完全二叉树的下标有如下特点,左孩子一定是奇数,右孩子一定是偶数
3>左节点的下标=父节点的下标2+1,右节点的下标=父节点的下标2+2
大根堆:每个节点比孩子节点都要大
小根堆:每个节点比孩子节点都要小
33,8,11,2,99,45,41,0,6,76,63,12,22,55,77,-12,-33,44,8,24,51,33,68,5
无序数组构建大根堆
按照数组顺序插入,跟父节点进行比较,如果比父节点大,则和父节点的值进行交换。
如果总数有n个数,则形成的完全二叉树有logn层,每次新增一个数不超过logn次操作。
整体复杂度nlogn
大根堆构建有序数组
选树顶的挑出来,然后将树末尾的数补到最顶,然后和两个孩子进行比较,大的孩子放到最顶,逐渐下放,形成新的大根堆。
逐渐循环,直到树空,排序完成。拿走的最大数放到末尾,但不再计入完全树。
每次构建一次操作次数不超过logn,一共有n个数进行n次操作
整体复杂度nlogn