1.怎么衡量一个排序算法的优劣?
时间复杂度、空间复杂度、稳定性
2.什么是时间复杂度?
假设机器执行一行基础代码需要执行一次运算
举例①:
#include<iostream>
using namespace std;
int main()
{
printf("hello world");\\执行一次
return 0;\\执行两次
}
需要执行两次运算
举例②:
#include<iostream>
using namespace std;
int main()
{
for(int i = 0;i < n; i ++)//执行n+1次
printf("hello world");//执行n次
return 0;//执行一次
}
需要执行(n+1+n+1)次
写一个函数表达式T(n)= 2n+1,为了简化运算,由此引入时间复杂度的概念
算法的时间复杂度,用来度量算法的运行时间,记作: T(n) = O(f(n))。它表示随着 n 的增大,算法执行需要的时间的增长速度可以用 f(n) 来描述。
概念很抽象,还是举例子:
①常数项对函数的增长速度影响并不大,所以当 T(n) = c,c 为一个常数的时候,我们说这个算法的时间复杂度为 O(1);如果 T(n) 不等于一个常数项时,直接将常数项省略。
printf("hello world");
return 0;
T(n) = 2,O(1)
②高次项对于函数的增长速度的影响是最大的。n^3 的增长速度是远超 n^2 的,同时 n^2 的增长速度是远超 n 的。 同时因为要求的精度不高,所以我们直接忽略低此项。
T(n) = n^3 + n^2 + 29,此时时间复杂度为 O(n^3)。
③ 函数的阶数对函数的增长速度的影响是最显著的,所以我们忽略与最高阶相乘的常数。
T(n) = 3n^3,此时时间复杂度为 O(n^3)。
总结:如果一个算法的执行次数是 T(n),那么只保留最高次项,同时忽略最高项的系数后得到函数 f(n),此时算法的时间复杂度就是 O(f(n))。
应用:
①对于多个循环,假设循环体的时间复杂度为 O(n),各个循环的循环次数分别是a, b, c...,则这个循环的时间复杂度为 O(n×a×b×c...)
②对于顺序执行的语句或者算法,总的时间复杂度等于其中最大的时间复杂度
③对于条件判断语句,总的时间复杂度等于其中 时间复杂度最大的路径 的时间复杂度。
3.排序算法的理解和比较
①冒泡排序(大的往后面排)
for(int i = 0; i < len - 1; i ++)
for(int j = 0; j < len - i - 1; j ++)
{
if(a[j]>a[j+1])
{
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
时间复杂度:O(n^2),空间复杂度O(1),稳定性:稳定
②选择排序(从后面选一个最小的放在前面)
for(int i = 0; i < len - 1; i ++)
{
int minindex = i;
for(int j = i; j < len - 1; j ++)
{
if(a[j]<a[minindex])
minindex = j;
}
int temp = a[i];
a[i] = a[minindex];
a[minindex] = temp;
}
时间复杂度:O(n^2),空间复杂度O(1),稳定性:不稳定
③插入排序(左边的默认排序,右边的从后向前扫描,直到找到一个合适的元素)
for(int i = 1; i < len; i ++)
{
int preindex = i - 1;
int current = a[i];
while(preindex >= 0&& a[preindex]>current)
{
a[preindex + 1] = a[preindex];
preindex --;
}
a[preindex + 1] = current;
}
时间复杂度:O(n^2),空间复杂度O(1),稳定性:稳定
④希尔排序(增量排序+插入排序)
int len = arr.length,temp;
for (int gap = len/2; gap> 0; gap = gap/2)
{
for (int i = 0; i < gap; i++)
{
//插入排序
}
时间复杂度:O(n^1.3)(最快时间复杂度,取决于它的增量),空间复杂度:O(1),稳定性:不稳定
⑤归并排序
递归方式求解
时间复杂度O(nlgn)空间复杂度O(n),稳定性:稳定
⑥快速排序
选定基准(一般是第一个),比基准小的放在前面,比基准大的放在后面
void quick_sort(int s[], int l, int r)
{
if (l < r)
{
//Swap(s[l], s[(l + r) / 2]); //将中间的这个数和第一个数交换 参见注1
int i = l, j = r, x = s[l];
while (i < j)
{
while(i < j && s[j] >= x) // 从右向左找第一个小于x的数
j--;
if(i < j)
s[i++] = s[j];
while(i < j && s[i] < x) // 从左向右找第一个大于等于x的数
i++;
if(i < j)
s[j--] = s[i];
}
s[i] = x;
quick_sort(s, l, i - 1); // 递归调用
quick_sort(s, i + 1, r);
}
}
时间复杂度:O(nlgn),空间复杂度:O(logn),稳定性:不稳定
4.平时都是怎么排序的?
①在C语言中(默认排序为升序):
qsort函数
声明:void qsort(要指向排序的第一个元素的地址,个数,类型,比较函数)
使用:假设对大小为7的已经赋值的int型的values数组排序
int compare(const void *a,const void *b)
{
return (*(int*)a) >= (*(int*)b);
}
qsort(values,7,sizeof(int),compare)
②在c++中(默认排序为升序):
sort函数(可以对任何类型的数组进行排序【char、结构体数组】)
声明:头文件#include<algorithm>
void sort(要指向的排序的第一个数组元素的地址,最后一个地址的下一位)
使用:假设对大小为7的已经赋值的int型的values数组排序
sort(values,values+7)
sort(values,values+7,cmp)
③在java中
使用:Arrays.sort(int start,int end)