大O表示法(向往罗马)

时间:2022-08-15 10:30:02

一、身在斯洛文尼亚的阿拉里克得到斯提里科被杀的消息后,仰天大笑:“终于没有人能阻止我去罗马了。”
当他手下的将军问:“不知大王打算走哪条路去罗马?”
西哥特王哈哈大笑,说出了那句千古名言:All roads lead to Rome

 

二、最近看了Mark Allen Weiss的《数据结构与算法分析---java语言描述》,看到第二章的算法分析,看到里面对算法时间预估方式用到的相对增长率(relative rate of growth)。同时为了准确计算不同算法的相对增加率,而使用到了大O标记法,这里结合对大学知识仅存的一点记忆,对大O标记法进行一个简单的对比分析。

三、首先用数组的排序证明大O标记法的使用方式

  1.冒泡排序

  

 *
* @author zmm
* @date 2017年7月30日
* @version 1.0
*
*/
public class SimpleSort {

public static void bubbleSort(final int[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("array");
}

int length = array.length;
for (int outLoop = length - 1; outLoop > 0; outLoop-- ) {
for (int innerLoop = 0; innerLoop < outLoop; innerLoop++) {
if (array[innerLoop] > array[innerLoop + 1]) {
swap(array, innerLoop, innerLoop + 1);
}
}
}
}

private static void swap(final int[] array, final int left, final int right) {
int temp = array[left];
array[left] = array[right];
array[right] = temp;
}
}

冒泡排序先从数组最左边开始,比较第1个和第2个元素的值,值比较高的往数组的高位排,然后依次比较第2和第3个元素,值比较大的往高位排,一直比较到倒数第2个和倒数第1个元素,这称为第一趟排序,这一趟就能确定数组中值最大的那个元素,并把这个最大的元素排到数组的最高位。
依次类推,第二趟排序会确定数组中第二大的元素,并把它排在最大的元素前边。

  二、选择排序

  

 * @version 1.0
*
*/
public class SimpleSort {

public static void selectionSort(final int[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("array");
}

int length = array.length;
int minIndex;
for (int outLoop = 0; outLoop < length - 1; outLoop++) {
minIndex = outLoop;
for (int innerLoop = outLoop + 1;innerLoop < length; innerLoop++) {
if (array[innerLoop] < array[minIndex]) {
minIndex = innerLoop;
}
}
swap(array, outLoop, minIndex);
}
}

private static void swap(final int[] array, final int left, final int right) {
int temp = array[left];
array[left] = array[right];
array[right] = temp;
}
}

选择排序的过程是从左向右扫描数组,并从中找出最小值的元素,把它放在左边已知的最小位置上,比如第一趟扫描,找出最小的元素后,将该元素放到数组的下标0处。第二趟扫描从下标1开始扫描,找出最小元素后,放到下标1处。总共需要扫描n-1次,就能使该数组处于有序状态。
  3、
/**
*
*
* @author beanlam
* @date 2016年3月9日 下午11:26:20
* @version 1.0
*
*/
public class SimpleSort {
public static void insertionSort(final int[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("array");
}

int length = array.length;
for (int outLoop = 1; outLoop < length; outLoop++) {
int temp = array[outLoop];
for (int innerLoop = outLoop - 1; innerLoop >= 0; innerLoop--) {
if (array[innerLoop] > temp) {
array[innerLoop + 1] = array[innerLoop];
if (innerLoop == 0) {
array[innerLoop] = temp;
}
} else {
array[innerLoop + 1] = temp;
break;
}
}
}
}

public static void insertionSort1(final int[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("array");
}

int length = array.length;
int temp;
int innerLoop;
for (int outLoop = 1; outLoop < length; outLoop++) {
temp = array[outLoop];
innerLoop = outLoop;
while (innerLoop > 0 && array[innerLoop - 1] >= temp) {
array[innerLoop] = array[innerLoop-1];
--innerLoop;
}
array[innerLoop] = temp;
}
}

private static void swap(final int[] array, final int left, final int right) {
int temp = array[left];
array[left] = array[right];
array[right] = temp;
}
}

插入排序的精髓在于先令局部有序,先令左边一部分数据有序,然后这部分有序的元素的下一位再与这些有序的元素比较,寻找合适自己站立的位置,插队排进去,插队也意味着右边的有序元素要挪动身子。
一下提供基于for循环和while循环的两种插入排序实现方式


四、结论分析

大O表示法只是一个粗略的估算值,它关注与随着数据量N的增大,算法速度的变化。

对于数组某个下标的赋值,算法消耗的时间是个常数K
对于线性的查找,算法的消耗时间与N成正比。
对于二分查找,算法消耗时间与log(N)成正比。

大O表示法通常会忽略常数,因为它关注的是算法的消耗时间随着N的变化而怎么变化。常数通常与处理器芯片或者编译器有关。