补充:动态规划与分治法的本质区别
① 分治法将分解后的子问题看成相互独立的。
② 动态规划将分解后的子问题理解为相互间有联系,有重叠部分。
算法 | 最好时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 排序方式 | 稳定性 | 算法设计策略 |
---|---|---|---|---|---|---|
冒泡【交】 | n【flag判是否交换】 | n2 | 1 | 内 | 稳定 | |
选择 | n2 | n2 | 1 | 内 | 不稳定 | |
插入 | n【基本有序】 | n2 | 1 | 内 | 稳定 | |
希尔【插入】 | 与增量相关还在研究 | n2 | 1 | 内 | 不稳定 | |
归并 | nlogn | nlogn | n | 外 | 稳定 | 分治 |
快速【交换】 | nlogn | n2【有序】 | logn~n | 内 | 不稳定 | 分治 |
堆【选择】 | 稳定 |
通常说空间复杂度一般指的是额外空间复杂度。
k: “桶”的个数
关键字相同的序列在排序前后,序列不变
排序时记录是否全部放在内存中
外排序需要多次内外存之间数据交换,接下来,主要看内排序。
比较排序适用于一切需要排序的情况。
比较排序最优的情况下,时间复杂度=O(n logn)
涉及算法注意观察交换三步骤 ! 标记
n 次比较相邻两数并交换找到最值,然后 n-1 次比较找到次值……较小的数字像气泡一样浮出。
平均时间复杂度=O(n2)
优化思路:如果在一次找较小(大)值的循环中都没有交换次序,那么意味着排序成功,接下来的循环也就可以省去。引入标记变量跳出循环。此时若有序,时间复杂度=O(n)
void bubbleSort(int *arr,int n)
{
int swap,i,j;
for(i=0;i<n-1&&flag;i++)
{
flag=0;//没有交换则跳出循环
for(j=0;j<n-1-i;j++)
{
if(arr[j]>arr[j+1])
{
swap=arr[j];//!
arr[j]=arr[j+1];//!
arr[j+1]=swap;//!
flag=1;//!
}
}}
}
n 次比较相邻两数后交换一次将最值放到位置上,然后 n-1 次比较找到次值……相比冒泡少了很多交换,多了一个暂存最值的空间。
平均时间复杂度=O(n2)
void selectSort(int *arr,int n)
{
int swap,i,j;
int min;
for(i=0;i<n-1;i++)
{
min=i;
for(j=i+1;j<n-1;j++)
{
if(arr[min]>arr[j])
{
min=j;
}
}
if(i!=min)
{
swap=arr[min];//!
arr[min]=arr[i];//!
arr[i]=swap;//!
}
}
}
直接插入原有的序列中
设立哨兵,临时存储和判断数组边界
平均时间复杂度=O(n2)
void insertSort(int *arr,int n)
{
int i,j;
for(i=2;i<n-1;i++)//插入,从第二个【0为哨兵】开始
{
if(arr[i-1]>arr[i])//i需要插入
{
arr[0]=arr[i];//!设置哨兵,将待插入数暂时存储在0
for(j=i-1;arr[j]>arr[0];j--)
{
arr[j+1]=arr[j];//!后移
}
arr[j+1]=arr[0];//!找到位置插入
}}
}
又称递减增量排序算法
先用直接插入法比较并调序交换 i 与 i+increment,然后缩小增量至1,由基本有序逐步达到有序。
涉及时间复杂度,增量的选取很关键,素数比较好
**时间复杂度=O(n1.3~2)**还在研究平均时间复杂度=O(n logn)
void shellSort(int *arr,int n)
{
int i,j;
int increment=n;
do
{
increment=increment/3+1;//随意选取的增量
for(i=increment+1;i<n-1;i++)//插入,从第二个分组【0为哨兵】开始
{
if(arr[i]<arr[i-increment])//i需要插入
{
arr[0]=arr[i];//!设置哨兵,将待插入数暂时存储在0
for(j=i-increment;j>0&&arr[j]>arr[0];j-=increment)
{
arr[j+increment]=arr[j];//!后移,寻找插入位
}
arr[j+increment]=arr[0];//!找到位置插入
}//if
}//for
}while(increment>1);
}
优先队列通常采用堆数据结构实现,向一个优先队列中插入元素的时间复杂度=O(logn)
在n个记录中选择一个最小的记录需要比较 n-1次,简单选择排序没有将之前的比较结果保存下来,所以次数较多。
特殊二叉树:
(1)满二叉树: 除叶子结点外,所有结点都有两个结点,叶子结点的left,right为NULL.
(2)哈夫曼树:又称最优二叉树,带权路径最短的树。哈夫曼编码应用哈夫曼树来进行编码压缩。
(3)完全二叉树: 除最底层的叶子结点外,其余层全满,而且最底层的叶子结点集中在左端。堆是一种特殊的完全二叉树
(4)平衡二叉树:左右两个子树的高度差的绝对值不超过 1。包括AVL树,红黑树.
堆是具有下列性质的完全二叉树:
某个节点的值总是不大于(大顶堆)或不小于(小顶堆)其父节点的值
在下面排序算法中,完全二叉树采用顺序存储于一维数组。
可推出 n/2+1~n 为叶子结点
(1)先构造一个大顶堆(堆顶一定是最大)
(2)然后将堆顶与堆最后一个元素交换,堆元素减一
(3)调整堆为大顶堆
(…)重复(2)(3)
时间复杂度=O(n logn)
void heapSort(int *arr,int n)
{
int i;
for(i=n/2;i>0;i--)//
{
heapAdjust(arr,i,n);//构造大顶堆,从底开始将最大的推到顶上
}
for(i=n;i>1;i--)//将大顶堆的顶挪到最后,一次得出最大值
{
swap(arr,1,i);//最大值
heapAdjust(arr,1,i-1);//堆减一,调整堆
}
}
/*i结点往前已经满足堆的定义*/
void heapAdjust(int *arr,int i,int n)
{
int j,temp;
temp=arr[i];//!
for(j=2*i;j<n-1;j*=2)/从左孩子开始向下筛选
{
if(j<n&&arr[j]<arr[j+1]) ++j;//j为较大的
if(temp>=arr[j]) break;//父节点>孩子,不交换
arr[i]=arr[j];//!
i=j;//!
}
arr[i]=temp;//!
}
采用分治法(Divide and Conquer)的典型的应用。
将已有序的子序列合并,得到序列。
若将两个有序表合并成一个有序表,称为二路归并。
时间复杂度=O(n logn)
算法采用递归调用,所以构建了一个函数。
#define ArrLen N
void merge(int arr[], int start, int mid, int end) {
int result[ArrLen];
int k = 0;
int i = start;
int j = mid + 1;
while (i <= mid && j <= end) {
if (arr[i] < arr[j]){
result[k++] = arr[i++];
}
else{
result[k++] = arr[j++];
}
}
if (i == mid + 1) {
while(j <= end)
result[k++] = arr[j++];
}
if (j == end + 1) {
while (i <= mid)
result[k++] = arr[i++];
}
for (j = 0, i = start ; j < k; i++, j++) {
arr[i] = result[j];
}
}
void mergeSort(int arr[], int start, int end) {
if (start >= end)
return;
int mid = ( start + end ) / 2;
mergeSort(arr, start, mid);
mergeSort(arr, mid + 1, end);
merge(arr, start, mid, end);
}
补:将由递归实现的归并排序算法,转化成迭代可以减少时间和空间性能上的损耗。
递归(recursion):描述以自相似方法重复事物的过程,在数学和计算机科学中,指的是在函数定义中使用函数自身的方法。(A调用A)
迭代(iteration):重复反馈过程的活动,每一次迭代的结果会作为下一次迭代的初始值。(A重复调用B)
分治算法设计策略
选定一个元素作为中心轴,通过一趟排序将要排序的数据分割成独立的两部分。
平均时间复杂度=O(n logn)
计数排序、基数排序、桶排序则属于非比较排序 ,均稳定。
算法时间复杂度为O(n):非比较排序只要确定每个元素之前的已有的元素个数即可,所有一次遍历即可解决。
但由于非比较排序需要占用空间来确定唯一位置,所以对数据规模和数据分布有一定的要求。
桶排序的核心思想是把要排序的数据分组放到桶里,然后再把每个桶里的数据单独进行排序,之后把数据取出,组成的序列就是有序的了。主要应用于处理数值分布比较均匀但是数据量很大的场景。
特殊的桶排序,每个桶只有一个数。
排列数值范围在0-n间的数列
(1)设置一个长度为n+1的数组,用下标表示数值,数组元素表示出现个数。
e.g:3个5 ==> arr[5]=3
(2)将个数转化为下标数值对应的位置,arr[3]=arr[2]+arr[1]
(3)依照原数组和范围数组,来排序