排序和搜索算法总结

排序算法


$O(n^2)$ 算法

Bubble Sort

  • 原理

    • 两层循环,内循环对比每一个元素及其右边的元素并将较大的元素swap到右边,在内循环结束后,当前最大的元素将会被移动到其正确的位置上。外层循环控制检查的次数,最多检查length-1次。
    • 在执行算法的过程中,数组分为两部分,左半部分是未排序的,右半部分是排好序的。
  • 时间复杂度:$O(n2)$。执行$O(n2) / 2$次比较,$O(n^2) / 4$次swap。原因是每次需要比较的元素都比上次少一个,因为当前最大元素已经被移动到正确位置上,所以共有$n + (n - 1) + (n - 2) + ... + 1$次比较,既$O(n^2) / 2$次比较。而每次比较移动两个元素,所以共有$O(n^2) / 4$次swap。

  • 最坏的情况出现在数组倒叙排列的情况下,在这种情况下每个元素都需要被移动。

代码如下:

public int[] bubbleSort(int[] nums) {
    if (nums == null || nums.length <= 0) {
        return nums;
    }

    int len = nums.length;
    for (int i = 0; i < len - 1; i++) {
        boolean swapped = false;
        for (int j = 0; j < len - 1 - i; j++) {
            if (nums[j] > nums[j + 1]) {
                swapped = true;
                swap(nums, j, j + 1);
            }
        }

        //if no numbers being swapped, the array is fully sorted already. 
        if (!swapped) {
            break;
        }
    }

    return nums;
}

private void swap(int[] nums, int i, int j) {
    int tmp = nums[i];
    nums[i] = nums[j];
    nums[j] = tmp;
}

Selection Sort

  • 原理

    • 将要排序的对象分成两部分,一部分是已排序的,一部分是未排序的。如果排序是由小到大,从后端未排序的部分选择一个最小值并放入前段已排序的部分的UI后一个。
  • 时间复杂度:$O(n2)$,$O(n2) / 2$ swap worst case。

代码如下:

public void selectionSort(int[] nums) {
    if (nums == null || nums.length <= 1) {
        return;
    }
    
    int min;

    int len = nums.length;
    for (int i = 0; i < len - 1; i++) {

        min = i;
        for (int j = i + 1; j < len; j++) {
            if (nums[j] < nums[min]) {
                min = j;
            }
        }

        swap(nums, i, min);
    }
}

private void swap(int[] nums, int i, int j) {
    int tmp = nums[i];
    nums[i] = nums[j];
    nums[j] = tmp;
}

Insert Sort

  • 原理

    • 将要排序的对象分作两部分,一个是已排序的,一个是未排序的。每次从后端未排序部分取得最前端的值,然后插入前段已排序的部分的适当位置。
  • 时间复杂度:$O(n^2)$,$O(n)$ in the best case.

public void insertSort(int[] nums) {
    if (nums == null || nums.length <= 1) {
        return;
    }
    int len = nums.length;
    
    for (int i = 1; i < len; i++) {
        int tmp = nums[i];
        int j = i;
        while (j > 0 && nums[j - 1] >= tmp) {
            nums[j] = nums[j - 1];
            j--;
        }

        nums[j] = tmp;
    }
}

$O(nlogn)$ 算法

Merge Sort

  • 原理

    • Merge sort利用了divide-and-conquer的思想。
    • 将原有的排序问题对半分割
    • 利用相同的方法分别解决每个子问题
    • 最终将子问题的结果组合起来。
    • 这就是divide-and-conquer的三个重要步骤:divide-conquer-combine
  • 步骤

    1. 对第一部分进行排序
    2. 对第二部分进行排序
    3. 将两部分分别排好序的子数组merge到一起
  • 时间复杂度:$O(n^2)$ on average。

算法如下:

public int[] mergeSort(int[] nums) {
    if (nums == null || nums.length <= 1) {
        return nums;
    }

    int len = nums.length;

    int mid = len / 2;

    int[] left = new int[mid];
    int[] right = new int[len - mid];

    copyArray(nums, left, 0, mid - 1);
    copyArray(nums, right, mid, len - 1);

    left = mergeSort(left);
    right = mergeSort(right);
    

    return merge(left, right);
}

private int[] merge(int[] left, int[] right) {
    int[] merged = new int[left.length + right.length];

    int index_left = 0;
    int index_right = 0;
    int index_m = 0;

    while (index_left < left.length && index_right < right.length) {
        if (left[index_left] < right[index_right]) {
            merged[index_m] = left[index_left++];
        } else {
            merged[index_m] = right[index_right++];
        }

        index_m++;
    }

    if (index_left < left.length) {
        for (int i = index_left; i < left.length; i++) {
            merged[index_m++] = left[i];
        }
    } else {
        for (int i = index_right; i < right.length; i++) {
            merged[index_m++] = right[i];
        }
    }

    return merged;
}

private void copyArray(int[] nums, int[] target, int start, int end) {
    int index = 0;
    for (int i = start; i <= end; i++) {
        target[index++] = nums[i];
    }
}

Quick Sort

  • 原理

    • 找一个pivot,将原数组分成两部分,然后对每一部分重复这个过程直到每一个元素都被选为pivot一次并得到处理。
  • 步骤

    • 选取一个pivot
    • 将原数组分成三部分:
      • 第一部分:所有小于pivot的元素
      • 第二部分:所有大于等于pivot的元素
      • 第三部分:pivot自己
    • 对第一部分和第二部分进行quick sort

代码如下:

public void quickSort(int[] nums) {
    if (nums == null || nums.length <= 1) {
        return;
    }

    quickSortHelper(nums, 0, nums.length - 1);
}

private void quickSortHelper(int[] nums, int left, int right) {
    if (left >= right) {
        return;
    } else {
        int pivot = nums[right];
        int pivotIndex = partition(nums, left, right, pivot);

        quickSortHelper(nums, left, pivotIndex - 1);
        quickSortHelper(nums, pivotIndex + 1, right);
    }
}

private int partition(int[] nums, int left, int right, int pivot) {
    int leftPointer = left - 1;
    int rightPointer = right;

    while (true) {
        while (leftPointer < rightPointer && nums[++leftPointer] < pivot);
        while (rightPointer > leftPointer && nums[--rightPointer] > pivot);

        if (leftPointer >= rightPointer) {
            break;
        } else {
            swap(nums, leftPointer, rightPointer);
        }
    }

    swap(nums, leftPointer, right);

    return leftPointer;
}

private void swap(int[] nums, int i, int j) {
    int tmp = nums[i];
    nums[i] = nums[j];
    nums[j] = tmp;
}

对于quick sort,需要注意的是其时间复杂度不一定总是$O(nlogn)$,在最坏情况下其时间复杂度是$O(n^2)$。当quick sort每次都将数组分割成两个长度分别为$n-1$和$0$的子数组时出现。这种情况会在数组是正序或者倒叙排列时出现。此时$T(n) = T(n-1) + O(n)$,从而使得时间复杂度为$O(n^2)$。那么我们怎么解决这个问题呢?

可以使用randomized quick sort来解决。在选取pivot的时候,我们不再使用最右边的元素作为pivot而是从数组中随机选择一个来作为pivot从而避免刚好选到最大值或者最小值的情况初选。通过这种方式,quick sort可以得到平均$O(nlogn)$的时间复杂度。

Heap Sort

待完善



搜索算法

$O(n)$算法

Linear Search

  • 原理

    • 将全部元素清点一遍,寻找需要的值。
  • 时间复杂度:$O(n)$

public int lineSearch(int[] nums, int target) {
    if (nums == null || nums.length == 0) {
        return -1;
    }
    
    for (int i = 0; i < nums.length; i++0 {
        if (nums[i] == target) {
            return i;
        }
    }
    
    return -1;
}

Quick Select

参见Quick select algorithm

$O(logn)$算法

Binary Search

  • 原理

    • 每次搜索扔掉一半的元素,只有能够线性定位的结构能够使用。(比如LinkedList就不能使用,因为无法再O(1)时间内直接得到中间点。)
    • 数组必须是排好序的
  • 时间复杂度:$O(logn)$

public int binarySearch(int[] nums, int target) {
    if (nums == null || nums.length == 0) {
        return -1;
    }
    
    int start = 0; 
    int end = nums.length - 1;

    while (start <= end) {
        int mid = start + (end - start) / 2; 
        //avoid int overflow
    
        if (nums[mid] == target) {
            return mid;
        }
        if (nums[mid] < target) {
            start = mid + 1;
        }
        if (nums[mid] > target) {
            end = mid - 1;
        }
    }
    
    return -1;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容

  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,743评论 0 33
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,183评论 0 52
  • 概述:排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    每天刷两次牙阅读 3,730评论 0 15
  • 学习超级个体第一周, 总结【我学到了什么】: 这周学习了主要是时间管理的知识,盖上所有笔记,能想起来的就是用潮汐A...
    雪雪Maisie阅读 293评论 0 2
  • 每每读到亦舒笔下的美丽女子,我就不由得想起我的好朋友阿洁,一个气质出众,美丽聪慧的女人。 阿洁三十出头,皮肤紧致,...
    瑞雪吉祥阅读 878评论 5 1