目录
时间频度
基本介绍
时间频度:一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间
就多。
一个算法中的语句执行次数称为语句频度或时间频度
。记为
T(n)
。
[
举例说明
]
举例说明-基本案例
比如计算
1-100
所有数字之和
,
我们设计两种算法:
忽略常数项
忽略低次项
忽略系数
时间复杂度
1)
一般情况下,
算法中的基本操作语句的重复执行次数是问题规模
n
的某个函数
,用
T(n)
表示,若有某个辅助函数
f(n)
,使得当
n
趋近于无穷大时,
T(n) / f(n)
的极限值为不等于零的常数,则称
f(n)
是
T(n)
的同数量级函数。
记作
T(n)=
O
( f(n) )
,称O
( f(n) )
为算法的渐进时间复杂度,简称时间复杂度。
2) T(n)
不同,但时间复杂度可能相同。 如:
T(n)=n
²
+7n+6
与
T(n)=3n
²
+2n+2
它们的
T(n)
不同,但时间复杂
度相同,都为
O(n
²
)
。
3)
计算时间复杂度的方法:
- 用常数 1 代替运行时间中的所有加法常数 T(n)=n²+7n+6 => T(n)=n²+7n+1
- 修改后的运行次数函数中,只保留最高阶项 T(n)=n²+7n+1 => T(n) = n²
- 去除最高阶项的系数 T(n) = n² => T(n) = n² => O(n²)
常见的时间复杂度实例
排序算法
冒泡排序
/**
* 冒泡排序
* 时间复杂度O(n*n)
* 结合图解,5个元素只需要排4趟,所以第一个for循环是length-1次
* 每趟里最多比较4次,且每到下一趟都会少比较一次。所以第二个for循环是 length-1-i次
* @param nums
* @return
*/
public static int[] bubbleSort(int[] nums){
System.out.println("没有排序前:"+Arrays.toString(nums));
for (int i = 0; i < nums.length - 1; i++) { //这里控制排多少躺
for (int j = 0; j < nums.length - i - 1; j++) { //这里控制一趟排多少次
if(nums[j] > nums[j+1]){
swap(nums,j,j+1);
}
}
System.out.println("第"+(i+1)+"次排序:"+Arrays.toString(nums));
}
return nums;
}
public static void swap(int[] nums,int i,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
可以稍稍优化一下,如果有某趟排序里没有发生过交换,那就直接结束排序。
/**
* 冒泡排序
* 时间复杂度O(n*n)
* 结合图解,5个元素只需要排4趟,所以第一个for循环是length-1次
* 每趟里最多比较4次,且每到下一趟都会少比较一次。所以第二个for循环是 length-1-i次
* @param nums
* @return
*/
public static int[] bubbleSort(int[] nums){
System.out.println("没有排序前:"+Arrays.toString(nums));
boolean flag = false; //表示变量,表示是否发生过交换
for (int i = 0; i < nums.length - 1; i++) { //这里控制排多少躺
for (int j = 0; j < nums.length - i - 1; j++) { //这里控制一趟排多少次
if(nums[j] > nums[j+1]){
flag = true; //发生过交换,排序继续进行
swap(nums,j,j+1);
}
}
if (!flag){ //取反,如果flag没有发生过交换,那就说明已经排序完成,直接结束排序
break;
}else {
flag = false; //重置flag
}
System.out.println("第"+(i+1)+"次排序:"+Arrays.toString(nums));
}
return nums;
}
两次排序就可以完成,无需排序4次。
选择排序
选择排序(
select sorting
)也是一种简单的排序方法。
它的基本思想是:第一次从
arr[0]~arr[n1]
中选取最小值,
与
arr[0]
交换,第二次从
arr[1]~arr[n-1]
中选取最小值,与
arr[1]
交换,第三次从
arr[2]~arr[n-1]
中选取最小值,与
arr[2]
交换,…,第
i
次从
arr[i-1]~arr[n-1]
中选取最小值,与
arr[i-1]
交换,…
,
第
n-1
次从
arr[n-2]~arr[n-1]
中选取最小值,
与
arr[n-2]
交换,总共通过
n-1
次,得到一个按排序码从小到大排列的有序序列。
/**
* 选择排序 O(n*n)
* 每次只比较length-1次
* 在每趟排序开始,先假设数组第i个数是最小数(第一轮就是下标为0的,第二轮就是下标为1的)
* 然后依次和后面的数进行比较,如果发现num[min]比后面的数大,那就将比较小的数的下标记录到min
* 在一趟比较完后,把i和min进行交换,如此执行到length-1次为止
* @param nums
* @return
*/
public static int[] selectSort(int[] nums){
System.out.println("排序前:"+Arrays.toString(nums));
for (int i = 0; i < nums.length - 1; i++) {
int min = i;
for (int j = i; j < nums.length; j++) {
if (nums[min] > nums[j]){
min = j;
}
}
BubbleSort.swap(nums,min,i);
System.out.println("第"+(i+1)+"次排序:"+Arrays.toString(nums));
}
return nums;
}
插入排序
插入排序,一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法 [1] 。插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动。
将一个数插入一个已经排好序的数据中。
- 第一次循环时,从第2个数开始处理。我们将第1个数作为已经排好序的数据:当第2个数 > 第1个数时,将第2个数放在第1个数后面一个位置;否则,将第2个数放在第1个数前面。此时,前两个数形成了一个有序的数据。
- 第二次循环时,我们处理第3个数。此时,前两个数形成了一个有序的数据:首先比较第3个数和第2个数,当第3个数 > 第2个数时,将第3个数放在第2个数后面一个位置并结束此次循环;否则,再和第1个数比较。如果第3个数 > 第1个数,则将第3个数插入第1个数和第2个数中间;否则,第3个数 < 第1个数,则将第3个数放在第1个数前面。此时,前三个数形成了一个有序的数据。
- 后续的数据同理处理,直至结束。
作者:程序员囧辉
链接:https://zhuanlan.zhihu.com/p/35328552
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
/**
* 插入排序 O(n*n)
* 将第一个元素看作是一个有序数组,然后从第二个元素开始,将元素依次插入到有序数组里
* 假设第二个元素34插入到有序数组内 34 < 101,34就会向后一个元素进行比较。发现没有元素或没有比自己小的了,就交换位置。
* @param arr
* @return
*/
public static int[] insertSort(int[] arr){
for (int i = 1; i < arr.length; i++) { //设arr[0]是一个有序数组,所以从arr[1]开始进行排序操作
if (arr[i] < arr[i - 1]){ //假设当前定位的数,比有序数组里最大的数要小(34 < 101,如果是334的话那就不需要排序了,占住当前的位置就行)
int temp = arr[i]; //为了插入进去,首先保存下要插入的数
int j = i; //记录下i的值,方便去有序数组里遍历查找插入位置
while (j > 0 && temp < arr[j - 1]){
//j必须大于0,否则就会数组越界。被插入的数如果小于当前遍历到的数,那就继续循环下去
// 直到找到最左一个位置或者一个比自己大的数
arr[j] = arr[j-1]; //每找到一个比temp大的数,那就这个数的位置往右挪一格
j--; //去遍历下一个数
}
arr[j] = temp;//当循环结束的时候,把temp插入进去
}
}
return arr;
}
希尔排序
简单插入排序存在的问题
我们看简单的插入排序可能存在的问题
.
数组
arr = {2,3,4,5,6,1}
这时需要插入的数
1
(
最小
),
这样的过程是:
{2,3,4,5,6,6}
{2,3,4,5,5,6}
{2,3,4,4,5,6}
{2,3,3,4,5,6}
{2,2,3,4,5,6}
{1,2,3,4,5,6}
结论
:
当
需要插入的数是较小的数时
,
后移的次数明显增多
,对
效率
有影响
.
希尔排序
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至
1
时
,整个文件恰被分成一组,算法便终止
希尔排序【交换式】
三轮拆分
public static void main(String[] args) {
int[] nums = new int[]{8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
shellSort(nums);
}
public static void shellSort(int[] nums) {
//shell第一轮
//因为第一轮是将10个数组分成5组
System.out.println("-----------第一轮--------------");
for (int i = 5; i < nums.length; i++) {
//遍历各组中所有的元素(共5组,每组两个元素),每次步长都是5
for (int j = i - 5; j >= 0; j -= 5) {
//如果当前元素大于加上步长后的那个元素,说明要进行交换
//这里看起来的感觉就是 8和3进行比较 因为j=0 j+5=5 即arr[0]与arr[5]
if (nums[j] > nums[j + 5]) {
BubbleSort.swap(nums, j, j + 5);//交换
}
}
}
System.out.println(Arrays.toString(nums));
System.out.println("-----------第二轮--------------");
//shell第二轮
//因为第一轮是将10个数组分成5组
for (int i = 2; i < nums.length; i++) {
//遍历各组中所有的元素(共5组,每组两个元素),每次步长都是5
for (int j = i - 2; j >= 0; j -= 2) {
//如果当前元素大于加上步长后的那个元素,说明要进行交换
//这里看起来的感觉就是 8和3进行比较 因为j=0 j+5=5 即arr[0]与arr[5]
if (nums[j] > nums[j + 2]) {
BubbleSort.swap(nums, j, j + 2);//交换
}
}
}
System.out.println(Arrays.toString(nums));
System.out.println("-----------第三轮--------------");
//shell第二轮
//因为第一轮是将10个数组分成5组
for (int i = 1; i < nums.length; i++) {
//遍历各组中所有的元素(共5组,每组两个元素),每次步长都是5
for (int j = i - 1; j >= 0; j -= 1) {
//如果当前元素大于加上步长后的那个元素,说明要进行交换
//这里看起来的感觉就是 8和3进行比较 因为j=0 j+5=5 即arr[0]与arr[5]
if (nums[j] > nums[j + 1]) {
BubbleSort.swap(nums, j, j + 1);//交换
}
}
}
System.out.println(Arrays.toString(nums));
}
将三轮汇总后
/**
* 希尔排序
* @param nums
*/
public static int[] shellSort(int[] nums) {
int count = 0;
for (int gap = nums.length / 2 ; gap > 0; gap/=2){
for (int i = gap; i < nums.length; i++) {
//遍历各组中所有的元素(共gap组,每组根据分组来决定元素数量),每次步长都是gap
for (int j = i - gap; j >= 0; j -= gap) {
//如果当前元素大于加上步长后的那个元素,说明要进行交换
//这里看起来的感觉就是 8和3进行比较 因为j=0 j+5=5 即arr[0]与arr[5]
if (nums[j] > nums[j + gap]) {
BubbleSort.swap(nums, j, j + gap);//交换
}
}
}
System.out.printf("希尔排序第%d轮后结果:",++count);
System.out.println(Arrays.toString(nums));
}
return nums;
}
希尔排序【移动法】
就是把gap里的循环替换,换成插入排序
/**
* 希尔排序【移动法】
* @param nums
*/
public static int[] shellSortInsert(int[] nums) {
int count = 0;
for (int gap = nums.length / 2 ; gap > 0; gap/=2){
//从第gap个元素,逐个对其所在的组进行直接插入排序
for (int i = gap; i < nums.length; i++) {
int j = i;
int temp = nums[j];
if(nums[j] < nums[j - gap]){
while (j - gap >= 0 && temp < nums[j - gap]){
//移动
nums[j] = nums[j - 1];
j--;
}
//当退出while循环后就说明找到位置了
nums[j] = temp;
}
}
System.out.printf("希尔排序第%d轮后结果:",++count);
System.out.println(Arrays.toString(nums));
}
return nums;
}
快速排序
核心思想:
1.在待排序的元素任取一个元素作为基准(通常选第一个元素,称为基准元素)
2.将待排序的元素进行分块,比基准元素大的元素移动到基准元素的右侧,比基准元素小的移动到作左侧,从而一趟排序过程,就可以锁定基准元素的最终位置
3.对左右两个分块重复以上步骤直到所有元素都是有序的(递归过程)
public static void main(String[] args) {
int[] nums = new int[]{-9,78,0,23,12,70, -1,900, 4561};
quickSort(nums,0,nums.length-1);
System.out.println(Arrays.toString(nums));
}
public static void quickSort(int[] arr,int left,int right){
int l = left; //左下标
int r = right; //右下标
int pivot = arr[(left+right)/2]; //确定中轴值 以数组中间为基准
//while循环的目的是让比基准值(pivot)小的放到左边
//比基准值(pivot)大的放到右边
while (l < r){
//在pivot的左边一直找,直到找到一个比pivot大或相等的
while (arr[l] < pivot){
l+=1;
}
//在pivot的右边一直找,直到找到一个比pivot小或相等的
while (arr[r] > pivot){
r-=1;
}
//l >= r 说明pivot的左右两边的值 左边全是小于pivot的值,右边全是大于pivot的值
if (l >= r){
break;
}
//找到以后开始交换
BubbleSort.swap(arr,l,r);
//如果交换完后发现这个arr[l] == pivot值,则r向左移动一步
if (arr[l] == pivot){
r-=1;
}else if(arr[r] == pivot){ //反之则l向右移动一步
l+=1;
}
}
if(l == r){
l+=1;
r-=1;
}
if (left < r){
quickSort(arr,left,r);
}
if (right > l){
quickSort(arr,l,right);
}
}
归并排序
归并排序(
MERGE-SORT
)是利用归并的思想实现的排序方法,该算法采用经典的
分治(
divide-and-conquer
)策略
(分治法将问题分
(divide)
成一些
小的问题然后递归求解
,而治
(conquer)
的阶段则将分的阶段得到的各答案
"
修
补
"
在一起,即分而治之
)
。
基本思想
归并排序的主要思想是分治法。主要过程是:
- 将n个元素从中间切开,分成两部分。(左边可能比右边多1个数)
- 将步骤1分成的两部分,再分别进行递归分解。直到所有部分的元素个数都为1。
- 从最底层开始逐步合并两个排好序的数列。
合并相邻有序子序列:
再来看看治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将
[4,5,7,8]
和
[1,2,3,6]
两个已经有序的子序列,合并为最终序列
[1,2,3,4,5,6,7,8]
,来看下实现步骤
public class MergeSort {
public static void main(String[] args) {
int[] arr = new int[] {8, 4, 5, 7, 1, 3, 6, 2};
mergeSort(arr,0,arr.length-1,new int[arr.length]);
System.out.println(Arrays.toString(arr));
}
// 归并
private static void mergeSort(int array[], int first, int last, int temp[]) {
if (first < last) {
int mid = (first + last) / 2;
mergeSort(array, first, mid, temp); // 递归归并左边元素
mergeSort(array, mid + 1, last, temp); // 递归归并右边元素
merge(array, first, mid, last, temp); // 再将二个有序数列合并
}
}
/**
* @param arr 待排序的数组
* @param left 左边有序序列的初始索引
* @param right 右边有序序列的最终索引
* @param mid 中间索引
* @param temp 临时数组
*/
public static void merge(int[] arr, int left,int mid, int right, int[] temp) {
//初始化i,表示左边有序序列的初始索引
//初始化j,表示右边有序序列的初始化索引
int i = left,j = mid + 1;
int m = mid,n = right;
int t = 0; //用来将数放到temp数组里的索引。指向temp数组的当前索引。
//(一)
//先把左右两边的数据按规则填充到temp数组
//直到左右两边的有序序列有一边处理完毕为止
while (i <= m && j <= n){ //两边的有序序列还未比较完,继续比较
if (arr[i] <= arr[j]){
//将两边数组的数进行比较,比较小的一方先进入temp数组
temp[t++] = arr[i++];
}else {
temp[t++] = arr[j++];
}
}
//(二)
//把有剩余数据的一方依次填充到temp去
//如果左边有剩下的
while (i <= m){
temp[t++] = arr[i++];
}
//如果右边有剩下的
while (j <= n){
temp[t++] = arr[j++];
}
//(三)
//将temp数组的元素拷贝到arr
for (i = 0; i < t; i++) {// 将排好序的数填回到array数组的对应位置
arr[left + i] = temp[i];
}
}
}