1.二分探索(⭐)
必要とする
- 二分探索アルゴリズムを自分の言語で説明できる
- 二分探索コードを手書きする機能
- 一部変更された試験方法に解答できるようにする
二分探索コードアルゴリズムのアイデア
-
前提: ソートされた配列 A があります (ソートが完了していると仮定します)。
-
左境界Lと右境界Rを定義し、探索範囲を決め、ループで二分探索を行う(ステップ3、4)
-
中間インデックスの取得 M = (L+R) /2
-
中間インデックスの値A[M]と検索対象の値Tを比較します。
① A[M] == T は見つかったことを意味し、中間のインデックスを返します
② A[M] > T、中央の値の右側にある他の要素は T より大きいため、比較する必要はありません。左側の中央のインデックスを見つけ、M - 1 を右側の境界として設定し、再度検索します。
③ A[M] < T、中央の値の左側にある他の要素は T より小さいため、比較する必要はありません。中央のインデックスの右側を見つけ、M + 1 を左側の境界として設定し、再度検索します。
-
L > R の場合、見つからないことを意味し、ループは終了する必要があります
コード
次のアルゴリズムは Arrays.binarySearch に基づいて実装されています。
public class BinarySearch {
public static void main(String[] args) {
int[] a = {
9, 3, 7, 2, 5, 8, 1, 4};
Arrays.sort(a);
int index = binarySearch(a, 7);
System.out.println(index);
}
private static int binarySearch(int[] a, int target) {
int l = 0, r = a.length - 1, mid;
while (l <= r) {
// 等价于(l+r)/2,但是如果r是Integer.MAX_VALUE的话,可能会导致溢出。
mid = (l + r) >>> 1;
// a[mid]与target相同
if (a[mid] == target) {
return mid;
}
// a[mid]比target大,说明在左边
if (a[mid] > target) {
r = mid - 1;
}
// a[mid]比target小,说明在右边
if (a[mid] < target) {
l = mid + 1;
}
}
return -1;
}
}
多肢選択式テスト
座右の銘: 奇数の 2 点で中央を取り、偶数の 2 点で中央を左へ
-
1、5、8、11、19、22、31、35、40、45、48、49、50 の順序リストがあります。二分探索値 48 のノードが見つかった場合、必要な比較回数は検索が成功しました
-
二分法を使用してシーケンス 1,4,6,7,15,33,39,50,64,78,75,81,89,96 内の要素 81 を見つける場合、() 比較を行う必要があります。
-
要素 128 個の配列内の数値を二分検索する場合、必要な比較の最大回数は次の回数です。
最初の 2 つの質問については、判断のための簡単な公式を覚えておいてください。奇数の 2 点が中央を取得し、偶数 2 点が中央の左を取得します。後者の質問については、次の式を知る必要があります。n = log2^Nしたがって、128 要素には 7 回必要になります。ここで、n は検索回数、N は要素数です。
2. バブルソート(⭐)
必要とする
- バブルソートアルゴリズムを自分の言語で説明できる
- バブルソートコードを手動で作成する機能
- バブルソートのいくつかの最適化方法を理解する
バブルソートアルゴリズムのアイデア
- 配列内の 2 つの隣接する要素のサイズを順番に比較します。a[j] > a[j+1] の場合、2 つの要素を交換します。両方の比較をバブリングのラウンドと呼びます。結果は、最大のものを配置します。要素をついに
- 配列全体がソートされるまで、上記の手順を繰り返します。
コード
public class BubbleSort {
public static void main(String[] args) {
int[] a = {
9, 3, 7, 2, 5, 8, 1, 4};
bubbleSort(a);
}
private static void bubbleSort(int[] a) {
// 循环次数只用循环a.length-1次,比如说8个元素,只用循环7次即可排序完成。
for (int j = 0; j < a.length - 1; j++) {
// 记录是否进行交换,如果没不再交换了就直接退出循环。
boolean flag = false;
// 只用比较a.length-1-j次,因为有j个数已经排好了,没必要进行比较了。
for (int i = 0; i < a.length - 1 - j; i++) {
if (a[i] > a[i + 1]) {
swap(a, i, i + 1);
flag = true;
}
}
// 如果这一遍循环没有进行交换,则说明数组已经排序好了,则直接退出循环。
if (!flag) {
break;
}
System.out.println(Arrays.toString(a));
}
}
private static void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
- 最適化ポイント 1: バブリングの各ラウンドの後、内側のループを 1 回削減できます (内側のループ条件 -j によって実現されます)。
- 最適化ポイント2: 一定のバブリングラウンドでやり取りがなければ、すべてのデータが揃っていることを意味し、外側のループを終了できます(タグ変数を設定して達成)
高度な最適化
public static void bubble_v2(int[] a) {
int n = a.length - 1;
while (true) {
int last = 0; // 表示最后一次交换索引位置
for (int i = 0; i < n; i++) {
System.out.println("比较次数" + i);
if (a[i] > a[i + 1]) {
Utils.swap(a, i, i + 1);
last = i;
}
}
n = last;
System.out.println("第轮冒泡"
+ Arrays.toString(a));
if (n == 0) {
break;
}
}
}
- バブリングの各ラウンド中に、最後の交換インデックスをバブリングの次のラウンドの比較回数として使用できます。この値が 0 の場合は、配列全体が正常であることを意味し、外側のループを終了するだけで済みます。
3. 選択ソート(不安定)
必要とする
- 選択ソートアルゴリズムを自分の言語で説明できる
- 選択ソートとバブルソートを比較する機能
- 不安定なソートと安定したソートを理解する
選択ソートアルゴリズムのアイデア
-
配列をソート済みとソートされていない 2 つのサブセットに分割します。各ラウンドで、ソートされていないサブセットから最小の要素が選択され、ソートされたサブセットに入れられます。
-
配列全体がソートされるまで、上記の手順を繰り返します。
コード
public class SelectionSort {
public static void main(String[] args) {
int[] a = {
9, 3, 7, 2, 5, 8, 1, 4};
selectionSort(a);
}
private static void selectionSort(int[] a) {
// 只用循环a.length-1次,比如说8个元素,只用循环7次即可排序完成。
// i变量代表着每轮选择的最小元素要放到的索引位置。
for (int i = 0; i < a.length - 1; i++) {
// 每轮循环找到的最小元素,初始值从i开始
int num = i;
// 从当前元素开始,与后面所有元素进行比较,选出最小的元素。
for (int j = num + 1; j < a.length; j++) {
// 如果遍历到某个元素比a[num]小,则将该元素下标赋值给num
if (a[num] > a[j]) {
num = j;
}
}
if (num != i) {
// 将这次循环找到的最小的元素放入i位置。
swap(a, num, i);
}
System.out.println(Arrays.toString(a));
}
}
private static void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
- 最適化ポイント: 交換の数を減らすために、まず各ラウンドで最小のインデックスを見つけ、各ラウンドの終わりに要素を交換します。
バブルソートと選択ソート
- 両方の平均時間計算量はO(n^2) です
- 選択範囲の並べ替えは、交換が少ないため (レコードの添字形式)、挿入 > 選択 > バブルよりも一般に高速です。
- ただし、コレクションが高度に順序付けされている場合は、選択よりもバブリングの方が優れています。
4. 挿入ソート
必要とする
- 挿入ソートアルゴリズムを自分の言語で説明できる
- 挿入ソートと選択ソートを比較する機能
挿入ソートアルゴリズムのアイデア
-
配列をソート領域と未ソート領域の 2 つの領域に分割し、各ラウンドで未ソート領域から最初の要素を取得し、ソート領域に挿入します (順序を保証する必要があります)。
-
配列全体がソートされるまで、上記の手順を繰り返します。
コード
public class InsertSort {
public static void main(String[] args) {
int[] a = {
9, 3, 7, 2, 5, 8, 1, 4};
insertSort(a);
}
private static void insertSort(int[] a) {
// i代表待插入元素的索引,从第二个元素开始,也就是下标为1的元素开始遍历。
for (int i = 1; i < a.length; i++) {
// 代表待插入的元素值
int target = a[i];
// sortedIndex代表已经排好序的元素末尾索引,初始值为i-1
int sortedIndex = i - 1;
for (; sortedIndex >= 0; sortedIndex--) {
// 将新元素与前面的元素一个个比较,如果小于前面的元素
if (target < a[sortedIndex]) {
// 前面的元素就往后移一位。
a[sortedIndex + 1] = a[sortedIndex];
} else {
// 退出循环,减少比较次数。
break;
}
}
a[sortedIndex + 1] = target;
System.out.println(Arrays.toString(a));
}
}
}
挿入ソートと選択ソート
- 両方の平均時間計算量はO(n^2) です
- ほとんどの場合、選択よりも挿入の方がわずかに優れています。挿入 > 選択 > バブル
- ソートセットの挿入時間の計算量は O(n)
利点: データ量が少ない並べ替えの場合は、挿入並べ替えが優先されます。
5. ヒルソート(不安定)
必要とする
- ヒルソートアルゴリズムを自分の言語で説明できる
ヒルソートアルゴリズムのアイデア
-
まず、(n/2, n/4 ... 1)などのギャップ シーケンスを選択します。n は配列の長さです。
-
各ラウンドでは、ギャップが等しい要素がグループとみなされ、グループ内の要素が 2 つの目的で挿入および並べ替えられます。
① 少数の要素の挿入とソートの速度が非常に速い
②グループ内の値が大きい要素ほど早く後ろに移動させる
-
差が徐々に減り、1になるとソート完了
6. クイックソート (不安定⭐)
必要とする
- クイックソートのアルゴリズムを自分の言語で説明できること
- 手書きの片側ループコードと両側ループコードのいずれかをマスターする
- クイックソートの特徴を説明できる
- Lomuto分割方式とホール分割方式のパフォーマンスの比較を理解する
クイックソートアルゴリズムのアイデア(合計)
-
- 基準点より小さい要素を 1 つのパーティションに入力し、基準点より大きい要素を別のパーティションに入力します。
- パーティショニングが完了すると、ピボット要素の位置が最終位置になります。
- サブパーティション要素の数が 1 以下になるまで、サブパーティション内で上記のプロセスを繰り返します。これは、分割統治の概念を反映しています。
- 上記の説明からわかるように、鍵は分割アルゴリズムにあり、一般的なものには、ロムート分割方式、双方向ループ分割方式、およびホール分割方式が含まれます。
片側巡回高速ソート (lomuto 分割スキーム)
-
データム要素として右端の要素を選択します
-
j ポインタは、参照点より小さい要素を見つける役割を担っており、見つかると i と交換されます。
-
i ポインタは、参照点より小さい要素の境界を維持し、各スワップのターゲット インデックスでもあります。
-
最後に、基準点が i と交換され、i は分割位置になります。
public class QuickSort1 {
public static void main(String[] args) {
int[] a = {
9, 3, 7, 2, 5, 8, 1, 4};
quickSort(a, 0, a.length - 1);
System.out.println(Arrays.toString(a));
}
private static void quickSort(int[] a, int low, int high) {
// 如果区间内没有元素,则直接return
if (low >= high) {
return;
}
// p是基准的索引值
int p = partition(a, low, high);
// 左边分区的递归
quickSort(a, low, p - 1);
// 右边分区的递归
quickSort(a, p + 1, high);
}
/**
* @param low 数组的左边界
* @param high 数组的右边界
*/
private static int partition(int[] a, int low, int high) {
// pivot是基准点元素,这里选择数组最右边的元素作为基准点
int pivot = a[high];
int i = low;
for (int j = low; j < high; j++) {
// 如果当前元素小于pivot,就让i与j互换
if (a[j] < pivot) {
swap(a, i, j);
i++;
}
}
// 交换i与pivot,让基准点左边全是小于pivot的,右边全是大于pivot的。
swap(a, high, i);
return i;
}
private static void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
双方向循環高速ソーティング (ホーア ホール パーティション スキームと厳密には同等ではありません)
- 左端の要素をデータム要素として選択します
- j ポインタは、参照点より小さい要素を右から左に検索する役割を果たします。
- i ポインタは、参照点より大きい要素を左から右に見つける役割を果たします。見つかったら、i、j が交差するまで 2 つの要素が交換されます。
- 最後に基準点をiと交換します(このときiとjは等しい)、iは分割位置になります
要点
- 基準点は左側にあり、j の後に i が続く必要があります。
- while( i < j && a[j] > ピボット ) j–
- while ( i < j && a[i] <= ピボット ) i++
内側のループで i<j を判定する必要があるのはなぜですか
配列が [5, 1, 2, 3, 6, 7, 8] の場合、最初に j–、後ろから前に 3 を見つけ、次に i++ を見つけ、6 を見つけてから交換すると、この時点で問題が発生します。時間。
public class QuickSort2 {
public static void main(String[] args) {
int[] a = {
9, 3, 7, 2, 5, 8, 1, 4};
quickSort(a, 0, a.length - 1);
System.out.println(Arrays.toString(a));
}
private static void quickSort(int[] a, int low, int high) {
// 如果区间内没有元素,则直接return
if (low >= high) {
return;
}
// p是基准的索引值
int p = partition(a, low, high);
// 左边分区的递归
quickSort(a, low, p - 1);
// 右边分区的递归
quickSort(a, p + 1, high);
}
private static int partition(int[] a, int low, int high) {
// 以左边的为基准元素
int pivot = a[low];
int i = low;
int j = high;
while (i < j) {
// j 从右边开始,找比pivot小的元素下标
while (i < j && a[j] > pivot) {
j--;
}
// i 从左边开始,找比pivot大的元素下标
while (i < j && a[i] <= pivot) {
i++;
}
swap(a, i, j);
}
// 将基准元素与j/i进行交换,此时i与j指向的是同一元素。
swap(a, low, j);
System.out.println(Arrays.toString(a));
return j;
}
private static void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
クイックソート機能
-
平均時間計算量は O(nlog2^n )、最悪の時間計算量は O(n^2)
-
データ量が多い場合、利点は非常に明白です
ロムートゾーニングスキームとホールゾーニングスキーム
- ホールの移動量は平均してロムートの 3 分の 1 です
安定ソートと不安定ソート
安定した並べ替えとは、同じ value を持つ要素の順序を乱すことなく、オブジェクト内の異なるフィールドによる複数の並べ替え (最初にスーツによる並べ替え、次にサイズによる並べ替えなど) を指します。また、不安定な並べ替えの場合はその逆を指します。