排序

/** 快速排序前端

  • 原理:基于分治的思想,在数组或链表中找到基准元素,小于基准元素的放在左边,大于或等于基准元素的放在右边,而后对基准元素两边再次进行排序。
  • 时间复杂度:
  • 最坏O(N^2)		说明:当待排序的序列在排序前是有序(顺序或逆序)的,此时的时间复杂度最差(这种状况下,快排退化成了冒泡排序)。
  • 平均O(NlogN)
  • 最好O(NlogN)
  • 说明:快速排序是对冒泡排序的一种改进。

*/ public class QuickSort {node

// ------- 1.数组的快速排序 -------

public int[] quickSort(int[] array, int left, int right) {
    if (left < right) {
        int pivotPosition = partition(array, left, right);  // 获取基准元素的下标
        quickSort(array, left, pivotPosition - 1);          // 对基准元素的左边进行排序
        quickSort(array, pivotPosition + 1, right);         // 对基准元素的右边进行排序
    }
    return array;
}

public int partition(int[] array, int left, int right) {

    int pivot = array[left];    // 选取数组的头元素做为基准元素
    int pivotPosition = left;   // 基准元素的索引

    /**
     * 循环结束后:
     *      1)pivotPosition后面的元素都大于等于基准元素pivot
     *      2)pivotPosition前面的元素(包括pivotPosition指向的元素):除了array[left]外,其它的元素都小于基准元素pivot
	 *			注:循环结束后,咱们还要将array[left]与array[pivotPosition]进行交换,以实现pivotposition前面的元素(不包括pivotPosition指向的元素)都小于基准元素
     */
    for (int i = pivotPosition + 1; i <= right; i++) { // i表示当前元素的索引
        if (array[i] < pivot) {

            pivotPosition++;
            if (pivotPosition != i) {
                swap(array, pivotPosition, i);
            }
        }
    }

    /**
     * 目的:
     *      将pivotPosition指向基准元素pivot,而且返回pivotPosition,以肯定递归函数quickSort()中left参数与right参数的值
     * 实现:
     *      交换基准元素与pivotPosition当前指向元素的位置。
     */
    if (pivotPosition != left) {
        array[left] = array[pivotPosition];
        array[pivotPosition] = pivot;
    }

    return pivotPosition;
}
// -------


// ------- 2.链表的快速排序 -------

public void quickSort(Node left, Node right) {

    if (left == null || right == null || left == right) return;

    Node pivotPosition = partition(left, right);    // 获取基准元素的下标
    quickSort(left, pivotPosition);                 // 对基准元素的左边进行排序
    quickSort(pivotPosition.next, right);           // 对基准元素的右边进行排序
}

public Node partition(Node left, Node right) {

    int pivot = left.value;     // 选取链表的头元素做为基准元素
    Node pivotPosition = left;  // 基准元素的引用

    for (Node i = pivotPosition.next; i != right.next; i = i.next) { // i表示当前元素的引用
        if (i.value < pivot) {

            pivotPosition = pivotPosition.next;
            if (pivotPosition != i) {
                swap(pivotPosition, i);
            }
        }
    }

    if (pivotPosition != left) {
        left.value = pivotPosition.value;
        pivotPosition.value = pivot;
    }

    return pivotPosition;
}
// -------

/**
 * 数组中,交换两个元素的位置
 */
private void swap(int[] array, int pivotPosition, int i) {
    int temp = array[pivotPosition];
    array[pivotPosition] = array[i];
    array[i] = temp;
}

/**
 * 链表中,交换两个元素的值
 */
private void swap(Node pivotPosition, Node i) {
    int temp = pivotPosition.value;
    pivotPosition.value = i.value;
    i.value = temp;
}

public static void print(int[] array) {
    for (int i = 0; i < array.length; i++) {
        System.out.print(array[i] + ",");
    }
    System.out.println();
}

public static void print(Node head) {

    ArrayList<Object> list = new ArrayList<>();
    while (head.next != null) {
        list.add(head.value);
        head = head.next;
    }
    list.add(head.value);
    System.out.println(list.toString());
}

public static void main(String[] args) {
    QuickSort qs = new QuickSort();

    // test array quicksort
    int[] array = {2, 5, 9, 3, 17, 4, 18, 7, 6};
    int[] sortArray = qs.quickSort(array, 0, array.length - 1);
    print(sortArray);

    // test linkedlist quicksort
    Node head = new Node(5);
    Node node1 = new Node(3);
    Node node2 = new Node(2);
    Node node3 = new Node(6);
    Node node4 = new Node(7);
    Node node5 = new Node(17);
    Node node6 = new Node(9);

    head.next = node1;
    node1.next = node2;
    node2.next = node3;
    node3.next = node4;
    node4.next = node5;
    node5.next = node6;

    qs.quickSort(head, node6);
    print(head);
}

}算法

============================================================================数组

/**函数

  • 冒泡排序
  • 原理:每一趟排序中,(从后往前)比较相邻的两个元素,大数下沉,小数上升,就像冒泡同样,故称为冒泡排序。
  • 时间复杂度:
  • 最坏O(N^2)	说明:当待排序的序列在排序前是逆序的,此时的时间复杂度最差
  • 平均O(N^2)
  • 最好O(N)	说明:当待排序的序列在排序前是顺序的,此时的时间复杂度最好

*/ public class BubbleSort {测试

public static void sort(int[] array) {

    // 总共冒泡array.length-1次,每次冒泡都会将未排序序列中的最小元素放到未排序序列的最前端。
    for (int i = 0; i < array.length-1; i++) {

        // 一趟冒泡(排序):从后往前比较相邻的元素,较小的元素往前冒泡,最终将最小的元素放到未排序序列的最前端。
        for (int j = array.length - 1; j > 0; j--) {
            if (array[j] < array[j - 1]) {
                swap(array, j, j - 1);
            }
        }
    }
}


/**
 * 数组中,交换两个元素的位置
 */
public static void swap(int[] array, int i, int j) {
    int temp = array[i];
    array[i] = array[j];
    array[j] = temp;
}

public static void main(String[] args) {

    int[] data = {2, 5, 9, 3, 17, 4, 18, 7, 6};
    sort(data);
    System.out.println(Arrays.toString(data));

}

}优化

============================================================================ui

/**this

  • 选择排序
  • 原理:首先从n个元素中找到最小的元素并与第一个元素进行交换,而后再从剩下的n-1个元素中找到最小的元素并与第二个元素进行交换,以此类推,直到剩下1个元素,排序完成。
  • 时间复杂度:
  • 最坏O(N^2)
  • 平均O(N^2)
  • 最好O(N^2)

*/ public class SelectSort {.net

public static void sort(int[] array) {

    // 将array[i]和array[i+1 ~ a.length]中的最小元素进行交换
    for (int i = 0; i < array.length - 1; i++) {

        // 记录本次循环中最小元素的下标,初始值为第一个元素的下标
        int minIndex = i;

        // 将本次循环中最小值的下标赋值给minIndex
        for (int j = i + 1; j < array.length; j++) {
            if (array[j] < array[minIndex]) {
                minIndex = j;
            }
        }

        if (minIndex != i) {
            // 将本次(第i次)循环中的最小值a[minIndex]与a[i]进行交换
            swap(array, minIndex, i);
        }
    }
}

/**
 * 数组中,交换两个元素的位置
 */
public static void swap(int[] array, int i, int j) {
    int temp = array[i];
    array[i] = array[j];
    array[j] = temp;
}

public static void main(String[] args) {
    int[] data = {2, 5, 9, 3, 17, 4, 18, 7, 6};
    sort(data);
    System.out.println(Arrays.toString(data));
}

}

============================================================================

/**

  • 插入排序
  • 原理:首先构建有序序列(将第一个元素做为有序序列),而后从后向前遍历已排序的序列,遍历的过程当中,将未排序的元素插入到合适的位置。
  • 时间复杂度:
  • 最坏O(N^2)
  • 平均O(N^2)
  • 最好O(N)

*/ public class InsertSort {

public static void sort(int[] array) {

    int insertElement;  // 要插入的元素的值

    int j;              // 有序序列的下标

    /**
     * 经过遍历前面的有序序列,将下标为i的元素插入到不大于本身的元素的后面。
     */
    for (int i = 1; i < array.length; i++) { // 将array[i]做为要插入的元素,从数组的第二个元素开始遍历。

        insertElement = array[i];   // 要插入的元素

        j = i - 1;                  // 有序序列最后一个元素的下标 (注:要插入的元素 前面的序列是有序序列,从后向前遍历前面的有序序列)。

        while (j >= 0 && array[j] > insertElement) { // 1.若是下标为j的元素大于要插入的元素,则将下标为j的元素向后移动一位。
            array[j + 1] = array[j];
            j--;
        }
        array[j + 1] = insertElement;               // 2.直到下标为j的元素不大于要插入的元素,则将要插入的元素插入到下标为j+1的位置。
    }
}

public static void main(String[] args) {
    int[] data = {2, 5, 9, 3, 17, 4, 18, 7, 6};
    sort(data);
    System.out.println(Arrays.toString(data));
}

}

============================================================================

/**

  • 希尔排序(最小增量排序)
  • 原理:
  • 1)先将要排序的序列按照某个增量(increment)分红若干个子序列,每一个子序列中元素的下标相差increment(即:第1个元素和第1+increment个元素组成了第一个子序列),而后对每一个子序列中的所有元素进行插入排序;
  • 2)再次将要排序的序列按照某个增量(increment/2)分红若干个子序列,每一个子序列中元素的下标相差increment/2,而后对每一个子序列中的所有元素进行插入排序;
  • 3)当增量小于1时,排序完成。
  • 说明:
  • 1)通常使用待排序序列长度的一半做为初始增量,且 下一次的增量=上一次的增量/2 即:increment1=length/2 increment2=increment1/2
  • 2)希尔排序是对插入排序的一个优化。
  • 时间复杂度:
  • 平均O(N^1.3)

*/ public class ShellSort {

public static void sort(int[] array) {

    if (array == null || array.length <= 1) {
        return;
    }

    int increment = array.length / 2;   // 增量

    while (increment > 0) {

        for (int i = 0; i < array.length; i++) {                            // 遍历待排序序列

            for (int j = i; j+increment < array.length; j += increment) {   // 对子序列进行插入排序
                if (array[j] > array[j+increment]) {
                    swap(array,j,j+increment);
                }
            }
        }

        increment = increment / 2;                                          // 设置新的增量,进行下一轮的遍历。
    }
}

/**
 * 数组中,交换两个元素的位置
 */
public static void swap(int[] array, int i, int j) {
    int temp = array[i];
    array[i] = array[j];
    array[j] = temp;
}

public static void main(String[] args) {
    int[] data = {2, 5, 9, 3, 17, 4, 18, 7, 6};
    sort(data);
    System.out.println(Arrays.toString(data));
}

}

============================================================================

/**

  • 归并排序
  • <p>

  • 原理:
  • 1)首先把待排序的序列分割成两个子序列,而后将子序列继续分割,直到子序列中只有一个元素为止(递归分割)。
  • 2)将这些子序列两两合并为一个有序的序列,最终将全部的子序列合并为一个大的有序序列,排序完成。
  • 时间复杂度:
  • 最坏O(NlogN)
  • 平均O(NlogN)
  • 最好O(NlogN)

*/ public class MergeSort {

public static int[] sort(int[] array, int low, int high) {

    if (low < high) {                   // 当low=high时,说明已经分解到单个元素了
        int mid = (low + high) / 2;
        sort(array, low, mid);          // 递归地对左半部分进行分解
        sort(array, mid + 1, high);     // 递归地对右半部分进行分解

        merge(array, low, mid, high);   // 分解已完成,将左子序列和右子序列进行合并
    }
    return array;
}

/**
 * 将左子序列和右子序列合并为一个有序序列。
 *
 * [@param](https://my.oschina.net/u/2303379) array
 * [@param](https://my.oschina.net/u/2303379) low
 * [@param](https://my.oschina.net/u/2303379) mid
 * [@param](https://my.oschina.net/u/2303379) high
 */
public static void merge(int[] array, int low, int mid, int high) {

    int[] temp = new int[high - low + 1];   // 临时数组,长度与原数组长度一致

    int i = low;            // 左边序列的第一个元素的下标
    int j = mid + 1;        // 右边序列的第一个元素的下标

    int k = 0;              // 临时数组temp的下标


    // eg: 左边的序列为:6 9 13  右边的序列为:4 7

    /**
     * 把较小的数先copy到临时数组temp中:                                       ==> 依次将 四、六、7 copy到临时数组temp中。
     */
    while (i <= mid && j <= high) {
        if (array[i] < array[j]) {
            temp[k++] = array[i++];
        } else {
            temp[k++] = array[j++];
        }
    }

    // 若是左边的序列中还有未copy过的元素,则把左边剩余的元素copy临时数组temp中。      ==> 依次将 九、13 copy到临时数组temp中。
    while (i <= mid) {
        temp[k++] = array[i++];
    }

    // 若是右边的序列中还有未copy过的元素,则把右边剩余的元素copy临时数组temp中。
    while (j <= high) {
        temp[k++] = array[j++];
    }

    // 用排好序的临时数组temp覆盖原数组
    for (int t = 0; t < temp.length; t++) {
        array[t + low] = temp[t];
    }
}

public static void main(String[] args) {
    int[] data = {2, 5, 9, 3, 17, 4, 18, 7, 6};
    sort(data,0,data.length-1);
    System.out.println(Arrays.toString(data));
}

}

============================================================================

/**

  • 【堆】
  • 堆的概念:堆是一棵顺序存储的彻底二叉树。
  • 堆分为大根堆和小根堆:
  • 大根堆:每一个节点的值不小于等于其左、右孩子的值
  • 小根堆:每一个节点的值不大于等于其左、右孩子的值
  • 【堆排序】
  • 1)概念:指利用堆的特性将待排序的序列进行排序。
  • 2)过程:
  • 1)将一个待排序的序列构形成一个堆:从最后一个非叶子结点向上遍历,直到全部的非叶子节点都遍历完毕(筛选法)。
  • 2)移走堆顶元素后,将剩余的元素再次构形成一个新的堆。
  • 3)大根堆的排序:
  • 1)将待排序的n个元素构形成一个大根堆。
  • 2)移走堆顶元素(即:将堆顶元素与堆数组的末尾元素进行交换,此时末尾元素为最大值)。
  • 3)将剩余的n-1个元素从新构形成一个大根堆,重复上面的步骤,直到剩余的元素只有一个时,排序完成。
  • 4)说明:将数组构形成初始堆时,若想升序排列构造大根堆,若想降序排列则构造小根堆。
  • 将一个彻底二叉树按层序排号依次存入数组:
  • 8
  • /   \
  • 3	   9		---按层序排号存入数组---> [8,3,9,5,7,4,2]
  • / \   / \
  • 5   7 4   2
  • 5)在数组中,很容易获得下面的结论:
  • 1)下标为i的节点,其父节点的下标为(i-1)/2
  • 2)下标为i的节点,其左子节点的下标为2*i+1,右子节点的下标为2*i+2
  • 6)使用场景:创建堆和调整堆的过程当中会产生比较大的开销,故堆排序适用于排序元素较多的时候。eg:
  • 问题:top K
  • 方案:
  • 第1步)分治:先将全部的数据按照hash方法分解成多个较小数据集。
  • 第2步)使用堆排序分别找出这几个较小数据集中的topK。
  • 第3步)将第2步中各数据集的topk放在一个集合里,而后求出最终的topK。
  • 7)复杂度:
  • 1)时间复杂度:最好、最坏、平均 的复杂度都是 O(nlogn),故堆排序堆输入的数据不敏感。
  • 建堆:O(n)
  • 调整:O(log n)
  • 2)空间复杂度:堆排序是就地排序,故其空间复杂度为O(1)。
  • 8)稳定性:排序先后相同元素间的相对位置可能会发生改变,故堆排序是一种不稳定的排序算法。

*/ public class HeapSort {

public static int[] array;

public static int adjustTime = 0;
public static int whileTime = 0;

public HeapSort(int[] array) {
    this.array = array;
}

/**
 * 根据子节点的索引来获取父节点的索引
 *
 * [@param](https://my.oschina.net/u/2303379) child
 * @return
 */
public static int parentIndex(int child) {
    return (child - 1) / 2;
}

/**
 * 根据父节点的索引来获取左子节点的索引
 *
 * @param parent
 * @return
 */
public static int leftChildIndex(int parent) {
    return parent * 2 + 1;
}

/**
 * 第一步:将待排序的n个元素构形成一个大根堆。
 *
 * 将数组初始化为大根堆:从下(最后一个非叶子节点)往上(堆的根节点)循环遍历。
 *
 *  要点:遍历存在左子节点的父节点,从最后一个非叶子结点开始遍历。
 *  说明:
 *      1)彻底二叉树是按层序排号存入数组的,故二叉树的最后一个节点(即:数组中索引值最大的元素)必定是叶子节点,故最后一个节点必定有父节点,且最后一个节点的父节点就是 堆最后一个非叶子节点。
 *      2)二叉树的最后一个节点的索引为array.length-1,则其父节点(即:最后一个非叶子节点)的索引为(array.length-1-1)/2,故咱们从array.length/2-1开始遍历!
 */
public static void initHeap() {

    // 从下往上的循环
    for (int parentIndex = parentIndex(array.length-1); parentIndex >= 0; parentIndex--) {
        adjustHeap(array, parentIndex, array.length - 1);
    }
}

/**
 * 对堆进行排序
 */
public static void sortHeap() {

    // array.length-1次 调整完成排序
    for (int i = array.length - 1; i > 0; i--) {

        // 第二步:将堆顶元素(数组中第一个元素)和当前未排序子序列中的最后一个元素交换
        swap(array, 0, i);

        // 第三步:交换后,将剩余的n-1个元素从新构形成一个大根堆
        adjustHeap(array, 0, i-1);
    }
}

/**
 * 调整堆:从上往下循环遍历,即 沿父节点的较大子节点向下调整
 *
 * @param array
 * @param parentIndex
 * @param maxIndex
 */
public static void adjustHeap(int[] array, int parentIndex, int maxIndex) { adjustTime++;

    int temp = array[parentIndex];              // 父节点的值
    int child = leftChildIndex(parentIndex);    // 左子节点的索引

    // 从上往下循环:沿父节点的较大子节点向下调整
    while (child <= maxIndex) {   // 左子节点必须在未排序子序列中

        whileTime++;    // 记录循环的次数,测试用。

        // 若当前节点(即:父节点)存在右子节点(且右子节点在未排序的子序列中),而且右子节点的值大于左子节点时,将右子节点的索引赋值给child
        if (child + 1 <= maxIndex && array[child + 1] > array[child]) {
            child++;   // 将左子节点转换为右子节点
        }

        // 此时,child表示 子节点中值最大的那个节点 的索引

        // 若当前节点(即:父节点)的值大于子节点的值时,直接退出。
        if (temp > array[child]) {
            break;
        } else {
            array[parentIndex] = array[child]; // 将子节点的值赋值给父节点

            // 选取子节点的左子节点继续向下调整(执行wile循环)
            parentIndex = child;
            child = leftChildIndex(parentIndex);
        }
    }
    // 若发生了交换,则parentIndex表示子节点的索引;若没有发生交换,则parentIndex仍旧表示父节点的索引。
    array[parentIndex] = temp;
}


public static void swap(int[] array, int i, int j) {
    int temp = array[i];
    array[i] = array[j];
    array[j] = temp;
}

public static void main(String[] args) {

    int[] array = {2, 13, 5, 7, 14, 6, 10, 8, 11, 4, 3, 9, 1, 12, 0};
    HeapSort heapSort = new HeapSort(array);
    heapSort.initHeap();
    heapSort.sortHeap();
    System.out.println("排序后数组" + Arrays.toString(heapSort.array) + " 调整次数" + adjustTime + " while循环次数" + whileTime);
}

}

相关文章
相关标签/搜索