zoukankan      html  css  js  c++  java
  • 面试题算法与排序(转)

    关键字: 算法---排序

    排序的关键字

    1. 时间复杂度:整个排序算法运行所需要的时间。

    2. 空间复杂度:排序算法运行过程汇总所需要额外空间

    3. 稳定性:若待排的序列中有大小相同的两个数,若整个排序过程中不存在两数次序交换的可能新内阁,则该排序算法是稳定的。

    4. in-place:算法使用的额外存储空间是常数级的

    一,最基本的冒泡排序——Bubble Sort。

    public void swap(int[] data, int i, int j) { if (i != j) {
            data[i] = data[i] + data[j];
            data[j] = data[i] - data[j];
            data[i] = data[i] - data[j];
        }
    }

    二,冒泡排序(递增)

    冒泡排序,是所有排序中最简单的一种,也是效率最低的一种,时间复杂度O(n2),空间复杂度O(n)。冒泡排序没有改变原始元素的相对位置,因此是稳定的排序。

    public void bubble_sort(int[] data) {
    for (int i = 0; i < data.length; i++) {
    for (int j = 0; j < data.length; j++) {
    if (data[j] > data[j + 1]) {
                    swap(data, j, j + 1);
                }
            }
        }
    }

    三,插入排序(递增)

    插入排序也是一种比较简单的排序方法,它的基本原理就好似我们打牌过程中摸牌理牌那一环,当你摸到一张牌后将其插入到合适的位置。

    插入排序首先定位一个数(一般从第二个开始),将这个数依次与位于它之前的数进行比较,经过一轮比较,找到它在这些数中适当的位置。然后定位下一个数,再找到合适的为止,依次进行直到最后一个数。

    例如(5 2 1 4 3),黑体为进行交换的两数。

    • 第一轮:

      2 5 1 4 3)

    • 第二轮:

      (2 1 5 4 3)

      1 2 5 4 3)

    • 第三轮:

      (1 2 4 5 3)

      (1 2 4 5 3)

      (1 2 4 5 3)

    • 第四轮:

      (1 2 4 3 5

      (1 2 3 4 5)

      (1 2 3 4 5)

      (1 2 3 4 5)排序完成

    public void insertion_sort(int[] data) {
    int key = 0;
    int j = 0;
    for (int i = 1; i < data.length; i++) {
            key = data[i];
            j = i - 1;
    while (j >= 0 && data[j] > key) {
                swap(data, j, j + 1);
                j = j - 1;
            }
        }
    }

    排序插入在数据集较大的时候效率会变得恨低,但是它易于实现,处理小型数据集时效率较高,同时也是稳定的,in-place的,它的时间复杂度是O(n2),空间复杂度是O(n)。

    四,选择排序

    选择排序的工作原理

    1. 找到数据集中的最小元素

    2. 将最小元素与未排序声誉元素的第一个元素交换

    3. 对剩余元素进行以上步骤

    它的时间复杂度是O(n2),空间复杂度是O(n),同插入排序类似,它也不适用于大数据集。但是它易于实现,也是一种in-place的排序算法。对于稳定性:简易实现是不稳定的,例如(3 5 5 2),在第二轮中第二个五会被认为是最小的,然后同第一个五进行交换。

    public void selection_sort(int[] data) {
    int minimum = 0;
    for (int i = 0; i < data.length - 1; i++) {
            minimum = i;
    for (int j = i + 1; j < data.length; j++) {
    if (data[j] < data[minimum]) {
                    minimum = j;
                }
            }
            swap(data, i, minimum);
        }
    }

    五,ELFHash

    public int ELFHash(String str, int number) {
        int hash = 0;
        long x = 0L;
        char[] array = str.toCharArray();
        for (int i = 0; i < array.length; i++) {
            hash = (hash << 4) + array[i];
            if ((x = (hash & 0xF0000000L)) != 0) {
                hash ^= (x >> 24);
                hash &= ~x;
            }
        }
        int result = (hash & 0x7FFFFFFF) % number;
        return result;
    }

    六,快速排序

    快速排序的步骤:

    1. 从数组中选出一个中枢数(pivot)

    2. 重新排列该数组,让数组中比该数小的数都排在该数的前面,比该数大的数都排在该数的后面。经过这次排序,该数处于其最终为止,并将原数组分为两个子数组(大于它的数组和小于它的数组),这就是分段的过程。

    3. 递归的排列各个子数组,直至最后整个数组排序完成。

    快速排序的平均时间复杂度为O(nlogn),空间复杂度依据各种实现方式有所不同。

    public int partition(int[] data, int left, int right, int pivotIndex) {
    int privotValue = data[pivotIndex];
        swap(data, pivotIndex, right); // Move pivot to end
    int storeIndex = left;
    for (int i = left; i < right; i++) {
    if (data[i] <= privotValue) {
                swap(data, i, storeIndex);
                storeIndex = storeIndex + 1;
             }
        }
        swap(data, storeIndex, right); // Move pivot to its final place
    return storeIndex;
    }

    public void quick_sort(int[] data, int left, int right) {
    if (right > left) {
    int pivotIndex = left;
    int pivotNewIndex = partition(data, left, right, pivotIndex);
            quick_sort(data, index, pivotNewIndex - 1);
            quick_sort(data, pivotNewIndex + 1, right);
        }
    }

    七,归并排序

    归并排序是一种基于比较的排序算法,在多数的实现方法中它是稳定的。归并排序可是由计算机祖师级人物——冯诺依曼提出的哦。

    归并排序的过程:

    1. 如果数据链表的长度为0或1,则返回

    2. 将原始数据链表对半分成两个子链表

    3. 对每个子链表递归的调用合并排序进行排序

    4. 合并两个子链表使其成为一个排序完成的链表

    归并排序的时间复杂度为O(nlogn),空间复杂度为O(n)。

    public List<Integer> mergesort(List<Integer> data) {
    if (data.size() <= 1) {
    return data;
        }
    int middle = data.size() / 2;

    List<Integer> left = new ArrayList<Integer>();
        List<Integer> right = new ArrayList<Integer>();
    for (int i = 0; i < middle; i++) {
            left.add(data.get(i));
        }
    for (int i = middle; i < data.size(); i++) {
            right.add(data.get(i));
        }
        left = mergesort(left);
        right = mergesort(right);
        List<Integer> result = merge(left, right);
    return result;
    }

    public List<Integer> merge(List<Integer> left, List<Integer> right) {
        List<Integer> result = new ArrayList<Integer>();
    while (left.size() > 0 && right.size() > 0) {
    if (((Integer)left.get(0)).intValue() <= ((Integer)right.get(0)).intValue()) {
                result.add(left.get(0));
                left.remove(0);
            } else {
                result.add(right.get(0));
                right.remove(0);
            }
        }
    if (left.size() > 0) {
    for (Iterator<Integer> iter = left.iterator(); iter.hasNext();) {
                result.add(iter.next());
            }
        }
    if (right.size() > 0) {
    for (Iterator<Integer> iter = right.iterator(); iter.hasNext();) {
                result.add(iter.next());
            }
        }
    return result;
    }

    八,堆排序

    堆排序是一种基于比较的排序算法,它比实现的较好的快速排序慢一些,但是它的平均时间复杂度为O(nlogn),空间复杂度为O(n),它是一种in-place的算法,但是确实不稳定的排序算法。

    最大堆和最小堆的定义:

    根结点(亦称为堆顶)的关键字是堆里所有节点关键字中最小者的堆成为最小堆。

    根结点(亦称为堆顶)的关键字是堆里所有节点关键字中最大者的堆成为最大堆。

    P.S.:

    堆中任一子树亦是堆。本文讨论的堆实际上是二插堆(Binary Heap),类似地可以定义k叉堆。

    堆排序的过程:

    1. 根据输入的数据集建立一个最大堆(最小堆)

    2. 进行堆排序,将Root(最大值)与堆的最后一个元素交换

    3. 堆调整,继续维护成为最大堆

    4. 进行步骤2和3,直至排序完成

    public void siftDown(int[] data, int start, int end) {
    int root = start;
    while ((2 * root + 1) <= end) {
    int child = root * 2 + 1;
    int (child < end && data[child] < data[child + 1]) {
                child++;
            }
    if (data[root] < data[child]) {
                swap(data, root, child);
                root = child;
            } else {
    break;
            }
        }
    }
    这段代码是堆排序的核心,对堆中的元素进行调整。简单来说做的工作就是,即针对一个堆点,将其与它孩子中较大的那个进行比较,若大不变,若小与该孩子交换位置,若交换后该堆点(处于原先它孩子的位置)仍有孩子则继续与孩子中较大的那个进行比较,若大不变,若下与该孩子交换位置,调整直至该堆点没有孩子结束。

    public void heapify(int[] data, int count) {
    int start = (count - 1) /2;
    while (start >= 0) {
            siftDown(data, start, count - 1);
            start = start - 1;
        }
    }

    这段代码是建堆的过程,找到最后一个有孩子的堆点,对该堆点进行调整,直至调整到Root。

    public void heapsort(int[] data, int count) {
        heapify(data, count);
    int end = count - 1;
    while (end > 0) {
            swap(data, 0, end);
            siftDown(data, 0, --end);
        }
    }

    这段代码解释了堆排序的过程,首先建堆,然后将Root与堆底元素交换,继而调整现有堆中Root(交换后的Root)位置,不断的调整直至遍历完整个堆。


    作者:高级测试开发网
    博客地址:https://seniortesting.club
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    觅踪2
    构建之法阅读笔记08
    觅踪1
    暑期第四周学习周总结
    暑期第三周学习周总结
    暑期第二周学习周总结
    数据库程序设计第九天--整合总结
    数据库程序设计第八天--隔离人员权限
    数据库程序设计第七天--隔离地权限
    数据库程序设计第六天--管理员权限
  • 原文地址:https://www.cnblogs.com/seniortestingdev/p/2384341.html
Copyright © 2011-2022 走看看