ソートは、要素のアレイまたはリストをオーダーし直すために、特定の順序であり、古典的な問題です。ソートアルゴリズムは、それぞれ独自の利点と制限があり、異なっています。これらのアルゴリズムは通常、自分自身を書きますが、プログラマの追求として、またはからそれらを理解するためにする必要はありませんが異なる角度のスケジューリング問題を解決するためのアイデアが。
学習アルゴリズムは、それはそれの原理を理解することです、どのように効率的な退屈なのですか?もちろん、動画の仕方は、ソートアルゴリズムのプロセスの鮮明な画像が表示された場合には、学習に非常に資します。visualgo.netは、最初の訪問の視覚サイトアルゴリズムであり、そして本当に輝いています。本論文では、一種のは、一般的に以下の要約に使用されます。
1.バブルソート
基本的な考え方は、バブルソートである:少しの要素が大きな要素で前方または後方に移動します。配列内のN個の要素があると仮定し、以下のように、バブルソートプロセスです。
- 現在の要素、後方に順次隣接する素子の各対を比較する(A、B)から
- >これらの2つの数の為替Bの場合
- 要素(N-2及びN-1要素)最後の比較までステップ1と2を繰り返し
- このとき、配列の最大要素が最終位置に既にされ、その後、Nマイナス1、工程1およびN = 1になるまで繰り返します
コアコードのバブルソート:
public static void bubbleSort(int[] a, int n) {
// 排序趟数,最后一个元素不用比较所以是 (n-1) 趟
for (int i = 0; i < n - 1; i++) {
// 每趟比较的次数,第 i 趟比较 (n-1-i) 次
for (int j = 0; j < n - 1 - i; j++) {
// 比较相邻元素,若逆序则交换
if (a[j] > a[j+1]) {
int tmp = a[j];
a[j] = a[j+1];
a[j+1] = tmp;
}
}
}
}
困難がされて境界を決定し、アルゴリズムの分析を:
- 平均時間計算量はO(N ^ 2) 、最良の場合はO(n)は、最悪の場合はOである(N ^ 2)
- 宇宙複雑性O(1)
- 安定ソートアルゴリズム(ソート順等しい要素の前後変わらず)
2. [ソート
選択した種類の基本的な考え方はこれです:たびに、すべての要素がソートされるまでソートされたシーケンスの終わりに最小(大)の要素を見つけるために、リストをソートすることはありません。アレイ及びL = 0におけるN個の要素があると仮定し、ソート処理を選択し、以下の通りであります:
- 最小要素の範囲[L ... N-1]中から見出さ添字X
- 第一切替位置Xと第二要素値L
- Lプラス1、L = N-2まで、上記の手順を繰り返し
ソートコアコードを選択します。
public static void selectionSort(int[] a, int n) {
// 排序趟数,最后一个元素是最大的不用比较所以是 (n-1) 趟
for (int i = 0; i < n-1; i++) {
int minIndex = i; // 无序列表中最小元素的下标
for (int j = i+1; j < n; j++) {
// 在无序列表中查找最小元素的小标并记录
if (a[j] < a[minIndex]) {
minIndex = j;
}
}// 将最小元素交换到本次循环的前端
int tmp = a[minIndex];
a[minIndex] = a[i];
a[i] = tmp;
}
}
アルゴリズム分析:
- 平均時間計算量はO(^ N-2) 、最良と最悪の場合も同様です
- 宇宙複雑性O(1)
- 不安定なソートアルゴリズム(同じ要素は前に変更し、ソート順の後よいです)
3.挿入ソート
基本的な考え方は、その挿入ソートである:各要素が挿入されるように、サイズに応じてソートされた配列の前に適切な位置に挿入されます。次のように挿入ソートプロセスは次のとおりです。
- 最初の要素の当初から、要素がソートしていると考えることができます
- ソート順の前方走査素子から次の要素を削除します
- 要素(ソート)が挿入される要素よりも大きい場合、それは次の位置に移動します
- あなたまで、ステップ3を繰り返し挿入するに等しいまたは要素未満の位置、低い位置に挿入される要素を見つけます
- あなたは配列の要素が取る終了するまで、手順2-5を繰り返します
コアコードの挿入ソート:
public static void insertionSort(int[] a, int n) {
// a[0] 看做已排序
for (int i = 1; i < n; i++) {
int x = a[i]; // 待插入元素
int j=i-1; // 插入的位置
while (j >= 0 && a[j] > x) {
a[j+1] = a[j]; // 为待插入元素腾地
j--;
}
a[j+1] = x; // 插入到下一个位置 j+1
}
}
アルゴリズム分析:
- 平均時間計算量はO(N ^ 2)、最良の場合はO(N)である、最悪の場合はO(N ^ 2)であります
- 宇宙複雑性O(1)
- 安定ソートアルゴリズム
4.シェルソート
シェルソートとも呼ばれる増分順、次の2つのプロパティに基づいて直接挿入アルゴリズムの改善です。
- ほとんど挿入ソートでソートされたデータ操作、高効率を、線形シーケンシング効率を達成することができます
- 挿入ソートのみができるのでしかし、挿入ソートは、一般的に非効率的である1ビットのデータを移動させます
改善のシェル・ソートは、1単位まで増分を減少、ソートセットに切断した後、挿入された異なるサブグループの増分アレイを使用することで、利点は、データが複数の要素、比較に横切って移動することができることですこれは、複数の要素の交換を排除することができます。次のように基本的なプロセスは次のとおりです。
- インクリメンタルシーケンス、一般的な使用を選択します
x/2
か、x/3+1
- 1までの最大増分パケットアレイを用いて配列、グループ内の挿入ソート、減少刻み
コアコード:
public static void shellSort(int[] a, int n) {
// 计算递增序列,3x+1 : 1, 4, 13, 40, 121, 364, 1093, ...
int h = 1;
while (h < n/3) h = 3*h + 1;
while (h >= 1) {// 直到间隔为 1
// 按间隔 h 切分数组
for (int i = h; i < n; i++) {
// 对 a[i], a[i-h], a[i-2*h], a[i-3*h]...使用插入排序
int x = a[i]; // 待插入元素
int j=i;
while (j >=h && x < a[j-h]) {
a[j] = a[j-h];// 为待插入元素腾地
j -= h;
}
a[j] = x; // 插入 x
}
// 递减增量
h /= 3;
}
}
丘ソーティング配列、挿入、次のデータと、図の可動を助けることができる上記の矛盾を理解例示スプリット:
アルゴリズム分析:
- 时间复杂度与选择的增量序列有关,可能的值时 O(n^2) > O(n^1.5) > O(nlg2n)
- 空间复杂度 O(1)
- 不稳定的排序算法
5. 归并排序(递归&非递归)
归并排序是分而治之的排序算法,基本思想是:将待排序序列拆分多个子序列,先使每个子序列有序,再使子序列间有序,最终得到完整的有序序列。归并排序本质就是不断合并两个有序数组的过程,实现时主要分为两个过程:
- 拆分 - 递归的将当前数组二分(如果N是偶数,两边个数平等,如果是奇数,则一边多一个元素),直到只剩 0 或 1 个元素
- 归并 - 分别将左右半边数组排序,然后归并在一起形成一个大的有序数组
二路归并递归实现,核心代码:
public static void mergeSort(int[] a, int low, int high) {
// 要排序的数组 a[low..high]
if (low < high) {// 是否还能再二分 low >= high (0 或 1 个元素)
int mid = low + (high - low) / 2; // 取中间值,避免 int 溢出
mergeSort(a, low, mid); // 将左半边排序
mergeSort(a, mid + 1, high); // 将右半边排序
merge(a, low, mid, high); // 归并左右两边
}
}
public static void merge(int[] a, int low, int mid, int high) {
int n = high - low + 1; // 合并后元素总数
int[] b = new int[n]; // 临时合并数组
int left = low, // 左边有序序列起始下标
right = mid + 1, // 右边有序序列起始下标
bIdx = 0;
// 按升序归并到新数组 b 中
while (left <= mid && right <= high) {
b[bIdx++] = (a[left] <= a[right]) ? a[left++] : a[right++];
}
// 右边序列已拷贝完毕,左边还有剩余,将其依次拷贝到合并数组中
while (left <= mid) {
b[bIdx++] = a[left++];
}
// 左边序列已拷贝完毕,右边还有剩余,将其依次拷贝到合并数组中
while (right <= high) {
b[bIdx++] = a[right++];
}
// 将归并后的数组元素拷贝到原数组适当位置
for (int k = 0; k < n; k++) {
a[low + k] = b[k];
}
}
数组拆分和方法调用的动态情况如下图(右键查看大图):
递归的本质就是压栈,对于 Java 来说,调用层次太深有可能造成栈溢出。一般的,递归都能转为迭代实现,有时迭代也是对算法的优化。
归并排序中的递归主要是拆分数组,所以,非递归的重点就是把这部分改成迭代,它们的终止条件不同:
- 递归是在遇到基本情况时终止,比如遇到了两个各包含1个元素的数组,从大数组到小数组,自顶向下
- 迭代则相反,自底向上,它首先按 1 切分保证数组中的每2个元素有序,然后按 2 切分,保证数组中的每4个元素有序,以此类推,直到整个数组有序
核心代码:
public static void unRecursiveMergeSort(int[] a, int n) {
int low = 0, high = 0, mid = 0;
// 待归并数组长度,1 2 4 8 ...
int len = 1; // 从最小分割单位 1 开始
while(len <= n) {
// 按分割单位遍历数组并合并
for (int i = 0; i + len <= n; i += len * 2) {
low = i;
// mid 变量主要是在合并时找到右半边数组的起始下标
mid = i + len - 1;
high = i + 2 * len - 1;
// 防止超过数组长度
if (high > n - 1) {
high = n - 1;
}
// 归并两个有序的子数组
merge(a, low, mid, high);
}
len *= 2; // 增加切分单位
}
}
算法分析:
- 平均时间复杂度,最佳和最差情况都是 O(nlgn)
- 空间复杂度 O(n),需要一个大小为 n 的临时数组
- 归并排序也是一个稳定的排序算法
6. 快速排序
快排可以说是应用最广泛的算法了,它的特点是使用很小的辅助栈原地排序。它也是一个分而治之的排序算法,基本思想是:选取一个关键值,将数组分成两部分,一部分小于关键值,一部分大于关键值,然后递归的对左右两部分排序。过程如下:
- 选取 a[low] 作为关键值-切分元素 p
- 使用两个指针遍历数组(既可一前一后,也可同方向移动),将比 p 小的元素前移,最后交换切分元素
- 递归的对左右部分进行排序
递归实现快排核心代码:
public static void quickSort(int[] a, int low, int high) {
if (low < high) {
int m = partition(a, low, high); // 切分
quickSort(a, low, m-1); // 将左半部分排序
quickSort(a, m+1, high); // 将右半部分排序
}
}
public static int partition(int[] a, int low, int high) {
// 将数组切分为 a[low..i-1], a[i], a[i+1..high]
int p = a[low]; // 切分元素
int i = low; // 下一个小于切分元素可插入的位置
// 从切分元素下一个位置开始,遍历整个数组,进行分区
for (int j = low + 1; j <= high; j++) {
// 往前移动比切分元素小的元素
if (a[j] < p && (i++ != j)) {
int tmp = a[j];
a[j] = a[i];
a[i] = tmp;
}
}
// 交换中枢(切分)元素
int tmp = a[low];
a[low] = a[i];
a[i] = tmp;
return i;
}
算法分析:
- 平均时间复杂度 O(nlgn),最佳情况 O(nlgn),最差情况是 O(n^2)
- 空间复杂度 O(lgn),因为递归,占用调用栈空间
- 快排是一个不稳定的排序算法
7. 堆排序
堆排序是利用堆这种数据结构设计的算法。堆可看作一个完全二叉树,它按层级在数组中存储,数组下标为 k 的节点的父子节点位置分别如下:
- 位置kの親ノードの位置(K-1)/ 2切り捨て
- k個の位置左の子ノード位置2K + 1
- k個の位置右の子の位置2K + 2
次のようにヒープを表します:
順序付けられたスタックの各ノードは、その子ノードの2以上で定義され、ルートノードは、バイナリヒープを命じたノードの最大値です。ヒープ・ソートは、基本的な手順の順序付けられたアレイを構築スタック連続スタックを再構築のプロセスを使用して、配列の残りの要素をルートノードを除去し、選択ソート幾分類似(しかしオーダー要素を降順に取り込まれます)。
- まず、最大のインデックス位置0にスタック全体の完了後、アレイ素子を素子(N-1)/ 2の構築物のスタックを使用する前に
- データアレイの交換が端へ、今回は、最大の配列を検索します
- 1スタックの小型化、繰り返し、ヒープ1の大きさになるまで1及び2ステップ
コアコード:
public static void heapSort(int[] a) {
int n = a.length - 1;
// 构建堆,一开始可将数组看作无序的堆
// 将从下标为 n/2 开始到 0 的元素下沉到合适的位置
// 因为 n/2 后面的元素都是叶子结点,无需下沉
for (int k = n/2; k >= 0; k--)
sink(a, k, n);
// 下沉排序
// 堆的根结点永远是最大值,所以只需将最大值和最后一位的元素交换即可
// 然后再维护一个除原最大结点以外的 n-1 的堆,再将新堆的根节点放在倒数第二的位置,如此反复
while (n > 0) {
// 将 a[1] 与最大的元素 a[n] 交换,并修复堆
int tmp = a[0];
a[0] = a[n];
a[n] = tmp;
// 堆大小减1
n--;
// 下沉排序,重新构建
sink(a, 0, n);
}
}
/** 递归的构造大根堆 */
private static void sink(int[] a, int k, int n) {
// 是否存在左孩子节点
while ((2*k+1) <= n) {
// 左孩子下标
int left = 2*k+1;
// left < n 说明存在右孩子,判断将根节点下沉到左还是右
// 如果左孩子小于右孩子,那么下沉为右子树的根,并且下次从右子树开始判断是否还要下沉
if (left < n && a[left] < a[left + 1])
left = left + 1;
// 如果根节点不小于它的子节点,表示这个子树根节点最大
if (a[k] >= a[left])
break; // 不用下沉,跳出
// 否则将根节点下沉为它的左子树或右子树的根,也就是将较大的值上调
int tmp = a[k];
a[k] = a[left];
a[left] = tmp;
// 继续从左子树或右子树开始,判断根节点是否还要下沉
k = left;
}
}
アルゴリズム分析:
- 平均時間の複雑さ、最高と最悪ですO(nlgn)
- 宇宙複雑さO(1)現場の順序で、
- 不安定なソートアルゴリズム
8.まとめ
読み取りは、時間をかけて、これは簡単に鑑賞するために、だけでなく、誰もが共有するために包む、忘れるたびに、問題を共有してくださいがあります。公共開始番号「ひらめきソース」、より多くのソースコード解析のための検索と車輪を構築します。