时间复杂度与空间复杂度相关概念
1、时间复杂度
时间复杂度可以认为是对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
常见的时间复杂度有:常数阶O(1),对数阶O(log2n),线性阶O(n), 线性对数阶O(nlog2n),平方阶O(n2)
时间复杂度O(1):算法中语句执行次数为一个常数,则时间复杂度为O(1),
2、空间复杂度
空间复杂度是指算法在计算机内执行时所需存储空间的度量,它也是问题规模n的函数
空间复杂度O(1):当一个算法的空间复杂度为一个常量,即不随被处理数据量n的大小而改变时,可表示为O(1)
空间复杂度O(log2N):当一个算法的空间复杂度与以2为底的n的对数成正比时,可表示为O(log2n)
ax=N,则x=logaN,
空间复杂度O(n):当一个算法的空间复杂度与n成线性比例关系时,可表示为0(n).
几种排序算法的稳定性、时间复杂度、空间复杂度的比较
1、直接插入排序
插入排序从小到大排序:首先位置1上的数和位置0上的数进行比较,如果位置1上的数大于位置0上的数,将位置0上的数向后移一位,将1插入到0位置,否则不处理。位置k上的数和之前的数依次进行比较,如果位置K上的数更大,将之前的数向后移位,最后将位置k上的数插入不满足条件点,反之不处理。
稳定性:稳定
时间复杂的:O(n^2)
空间复杂度:O(1)
若待排序列有序:时间复杂度为O(n)
#include <stdio.h>
void InsertSort(int *arr,int len)
{
int i;
int j;
int tmp;
for(i = 1;i < len ;i++)//找位置
{
tmp = arr[i];
for(j = i - 1;j >= 0;j--)//从后向前找
{
if(arr[j] <= tmp)
{
break;
}
else
{
arr[j + 1]=arr[j];
}
}
arr[j+1] = tmp;
}
}
2、希尔排序
希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小, 插入排序对于有序的序列效率很高。所
稳定性:不稳定
时间复杂度:O(n^1.3~n^1.5)
空间复杂度:O(1)
void Shell(int *arr,int len ,int gap)//gap:为增量(间隔值数组)
{
int i;
int j;
int tmp;
for(i = gap;i <len ;i++)//
{
tmp = arr[i];
for(j = i - gap;j >= 0;j -=gap )//同组前一个
{
if(arr[j] <= tmp)
{
break;
}
arr[j+gap] = arr[j];
}
arr[j + gap] = tmp;
}
}
void ShellSort(int *arr,int len)
{
int brr [] = {5,3,1};
for (int i = 0;i < sizeof(brr)/sizeof brr[0];i++)
{
Shell(arr,len,brr[i]);
}
}
3、冒泡排序
冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。
1、冒泡排序是一种用时间换空间的排序方法,n小时好
2、最坏情况是把顺序的排列变成逆序,或者把逆序的数列变成顺序,最差时间复杂度O(N^2)只是表示其操作次数的数量级
3、最好的情况是数据本来就有序,复杂度为O(n)
稳定性:稳定
时间复杂度:O(n^2)
空间复杂度:O(n)
将最大数沉底:
void BubbleSort(int *arr,int len)
{
int i;
int j;
int tmp;
/*for(i = 0;i < len - 1;i++)
{
for(j = 0;j+1 < len - i;j++)
{
if(arr[j] > arr[j + 1])
{
tmp = arr[j];
arr[j] = arr[j + 1];
arr[j +1] = tmp;
}
}
}*/
//进行优化
bool flg;
for(i = 0;i < len - 1;i++)
{
flg = false ;//为假,直接退出
for(j = 0;j < len - 1;j++)
{
if(arr[j] > arr [j +1])
{
tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
flg = true;//为真,交换
}
}
if (!flg)
{
return ;
}
}
}
4、快速排序
快速排序从小到大排序:在数组中随机选一个数(默认数组首个元素),数组中小于等于此数的放在左边,大于此数的放在右边,再对数组两边递归调用快速排序,重复这个过程。
①递归实现
稳定性:不稳定
时间复杂度:O(nlogn)
空间复杂度:O(logn)
1、n大时好,快速排序比较占用内存,内存随n的增大而增大,但却是效率高不稳定的排序算法。
2、划分之后一边是一个,一边是n-1个,
这种极端情况的时间复杂度就是O(N^2)
3、最好的情况是每次都能均匀的划分序列,O(N*log2N)
快速排序一次划分,如果划分段<=1,则不再进行划分
static int partition(int *arr,int low,int high)
{
int tmp = arr[low];//取一个数为基准
while (low < high)
{
while ((low < high) && (arr[high] >=tmp))
{
high --;
}
if(low == high)
{
break;
}
else
{
arr[high]=arr[low];
}
}
arr[low] = tmp;
return low;
}
static void Quick(int *arr,int low,int high)
{
int par = partition (arr,low,high);
if(low + 1 < par)
Quick(arr,low,par-1);
if(par < high - 1)
Quick(arr,par+1,high);
}
//为了保持接口一样,用递归无法解决,所以添加此函数
void QuickSort(int *arr,int len)
{
Quick (arr,0,len-1);
}
②非递归实现
稳定性:不稳定
时间复杂度:O(n)
空间复杂度:O(logn)
void QuickSort2(int *arr,int len)
{
Seqstack s;
InitStack(&s);
int low = 0;
int high = len - 1;
int par = partition(arr,low,high);
if(low + 1 <par)//入栈
{
push (&s,low);
push (&s,par - 1);
}
if(par +1 < high)
{
push(&s,par+1);
push(&s,high);
}
while(!IsEmpty(&s))//出栈
{
pop(&s,&high);//取数据
pop(&s,&low);
par = partition(arr,low,high);
if(low + 1 <par)//入栈
{
push (&s,low);
push (&s,par - 1);
}
if(par +1 < high)
{
push(&s,par+1);
push(&s,high);
}
}
Destroy(&s);
}
5、选择排序
选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个 元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么 交换后稳定性就被破坏了。
稳定性:不稳定
时间复杂度:O(n^2)
空间复杂度:O(1)
void SelectSort(int *arr,int len)
{
int minIndex;
int i;
int j;
int tmp;
for(i = 0;i < len;i++)
{
minIndex = i;
for(j = i+1;j < len;j++)
{
if(arr[minIndex ] > arr[j])
{
minIndex =j;
}
}
if(i !=minIndex )
{
tmp = arr[i ];
arr[i] = arr[minIndex ];
arr[minIndex ]=tmp;
}
}
}
6、归并排序
归并排序是把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的段序列合并成一个有 序的长序列,不断合并直到原序列全部排好序。
稳定性:稳定
时间复杂度:O(nlogn)
空间复杂度:O(n)
n大时好,归并比较占用内存,内存随n的增大而增大,但却是效率高且稳定的排序算法。
static void Merge(int *arr,int len,int gap)//gap:一个归并段的长度
{
int low1 = 0;//第一个归并段的起始下标
int high1 = low1+gap-1;//第一个归并段的结束下标
int low2 = high1+1;//第二个归并段的起始下标
int high2 = low2+gap<len?low2+gap-1:len-1;//第二个归并段的结束下标
//额外创建数组用来存放数据
int *brr = (int *)malloc (len*sizeof(int));
int i= 0;//brr下标
//判断有两个归并段
while(low2 <len)
{
//①两个归并段都有数据
while (low1 <= high1 && low2 <= high2 )
{
if(arr[low1] <=arr[low2])
{
brr[i++] = arr[low1++];
}
else
{
brr [i++] = arr[low2++];
}
}
//②一个归并段已经没有数据,另一个还有
while(low1 <=high1)
{
brr[i++] = arr[low1++];
}
while(low2 <= high2)
{
brr[i++] = arr[low2++];
}
//进入下两个归并段
low1 = high2 + 1;
high1 = low1 + gap -1;
low2 = high1 + 1;
high2 = low2+gap<len?low2+gap-1:len-1;
}
//③只有一个归并段
while(low1 < len)
{
brr[i++] = arr[low1 ++];
}
//将归并后的结果拷贝到arr中
for (i =0; i<len;i++)
{
arr[i] = brr[i];
}
free(brr);
}
void MergeSort(int *arr,int len)
{
for(int i= 1;i < len;i*=2)
{
Merge(arr,len,i);
}
}
7、堆排序
堆排序从小到大排序:首先将数组元素建成大小为n的大顶堆,堆顶(数组第一个元素)是所有元素中的最大值,将堆顶元素和数组最后一个元素进行交换,再将除了最后一个数的n-1个元素建立成大顶堆,再将最大元素和数组倒数第二个元素进行交换,重复直至堆大小减为1。
稳定性:不稳定
时间复杂度:O(nlogn)
空间复杂度:O(1)
堆:大根堆、小根堆
//一次堆调整 O(logn) O(1)
void HeapAdjust(int *arr,int start,int end)
{
int tmp = arr[start];
int parent = start;
for(int i =2*start + 1;i <= end;i = 2*i +1)
{
//i保存左右孩子较大值的下标
if(i+1 <=end && arr[i] < arr[i+1])
{
i++;
}
if (arr[i] > tmp)
{
arr[parent ] = arr[i];
parent = i;
}
else
{
break;
}
}
arr [parent ] = tmp;
}
//建立大根堆 O(nlogn)
void HeapSort(int *arr,int len)
{
int i;
int j;
int tmp;
for(i = (len - 1 -1)/2;i >= 0;i--)
{
HeapAdjust (arr,i,len-1);
}
for(i =0;i<len-1;i++)
{
tmp = arr[0];
arr[0] = arr[len-1-i];
arr[len-1-i] = tmp;
HeapAdjust (arr,0,len-1-i-1);
}
}
8、基数排序
稳定性:稳定
时间复杂度:O(d^n)
空间复杂度:O(n)
//用队列实现
//获取最大值位数
static int GetMaxFigure(int *arr,int len)
{
int max = arr[0];
int count = 0;
for(int i = 0;i < len;i++)
{
if(max < arr[i])
{
max = arr[i];
}
}
do
{
count++;
max /= 10;
}while(max!=0);
return count;
}
//得到十进制数字Num右数第figure位的值
//figure从0开始,如(123,0)->3
static int GetFigure (int num,int figure)
{
for (int i =0;i < figure;i++)
{
num/=10;
}
return num%10;
}
//figure 为十进制数字右数第figure位
//figure从0开始
static void Radix(int *arr,int len ,int figure)
{
HNode head[10];//创建队头数组
int i;
int index;
for(i = 0;i < 10;i++)
{
InitQueue(&head[i]);//初始化队头
}
for (i = 0;i <len;i++)//遍历数组
{
//按个位数将数放到对应的地方
index = GetFigure(arr[i],figure);
push(&head[index],arr[i]);
}
int j=0;
for(i = 0;i < len;)
{
if(pop(&head[j],&arr[i]))
{
i++;
}
else
{
j++;
}
}
}
void RadixSort(int *arr,int len)
{
int count = GetMaxFigure (arr,len);
for (int i = 0;i < count ;i++)
{
Radix (arr,len,i);
}
}