Re-translate the Japanese version (#1871)

* Retranslate Japanese docs with GPT-5.4

* Retranslate Japanese code with GPT-5.4
This commit is contained in:
Yudong Jin
2026-03-30 07:30:15 +08:00
committed by GitHub
parent fe6443235b
commit d7b2277d2b
1444 changed files with 83312 additions and 8363 deletions
@@ -0,0 +1,102 @@
/**
* File: array.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_array_and_linkedlist
import java.util.concurrent.ThreadLocalRandom
/* 要素へランダムアクセス */
fun randomAccess(nums: IntArray): Int {
// 区間 [0, nums.size) からランダムに数字を 1 つ選ぶ
val randomIndex = ThreadLocalRandom.current().nextInt(0, nums.size)
// ランダムな要素を取得して返す
val randomNum = nums[randomIndex]
return randomNum
}
/* 配列長を拡張する */
fun extend(nums: IntArray, enlarge: Int): IntArray {
// 拡張後の長さを持つ配列を初期化する
val res = IntArray(nums.size + enlarge)
// 元の配列の全要素を新しい配列にコピー
for (i in nums.indices) {
res[i] = nums[i]
}
// 拡張後の新しい配列を返す
return res
}
/* 配列の index 番目に要素 num を挿入 */
fun insert(nums: IntArray, num: Int, index: Int) {
// インデックス index 以降の全要素を 1 つ後ろへ移動する
for (i in nums.size - 1 downTo index + 1) {
nums[i] = nums[i - 1]
}
// index の要素に num を代入する
nums[index] = num
}
/* index の要素を削除する */
fun remove(nums: IntArray, index: Int) {
// インデックス index より後ろの全要素を 1 つ前へ移動する
for (i in index..<nums.size - 1) {
nums[i] = nums[i + 1]
}
}
/* 配列を走査 */
fun traverse(nums: IntArray) {
var count = 0
// インデックスで配列を走査
for (i in nums.indices) {
count += nums[i]
}
// 配列要素を直接走査
for (j in nums) {
count += j
}
}
/* 配列内で指定要素を探す */
fun find(nums: IntArray, target: Int): Int {
for (i in nums.indices) {
if (nums[i] == target)
return i
}
return -1
}
/* Driver Code */
fun main() {
/* 配列を初期化 */
val arr = IntArray(5)
println("配列 arr = ${arr.contentToString()}")
var nums = intArrayOf(1, 3, 2, 5, 4)
println("配列 nums = ${nums.contentToString()}")
/* ランダムアクセス */
val randomNum: Int = randomAccess(nums)
println("nums からランダムな要素 $randomNum を取得")
/* 長さを拡張 */
nums = extend(nums, 3)
println("配列の長さを 8 まで拡張し,nums = ${nums.contentToString()}")
/* 要素を挿入する */
insert(nums, 6, 3)
println("インデックス 3 に数値 6 を挿入し,nums = ${nums.contentToString()}")
/* 要素を削除 */
remove(nums, 2)
println("インデックス 2 の要素を削除し,nums = ${nums.contentToString()}")
/* 配列を走査 */
traverse(nums)
/* 要素を探索する */
val index: Int = find(nums, 3)
println("nums 内で要素 3 を検索し,インデックス = $index")
}
@@ -0,0 +1,88 @@
/**
* File: linked_list.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_array_and_linkedlist
import utils.ListNode
import utils.printLinkedList
/* 連結リストでノード n0 の後ろにノード P を挿入する */
fun insert(n0: ListNode?, p: ListNode?) {
val n1 = n0?.next
p?.next = n1
n0?.next = p
}
/* 連結リストでノード n0 の直後のノードを削除する */
fun remove(n0: ListNode?) {
if (n0?.next == null)
return
// n0 -> P -> n1
val p = n0.next
val n1 = p?.next
n0.next = n1
}
/* 連結リスト内で index 番目のノードにアクセス */
fun access(head: ListNode?, index: Int): ListNode? {
var h = head
for (i in 0..<index) {
if (h == null)
return null
h = h.next
}
return h
}
/* 連結リストで値が target の最初のノードを探す */
fun find(head: ListNode?, target: Int): Int {
var index = 0
var h = head
while (h != null) {
if (h._val == target)
return index
h = h.next
index++
}
return -1
}
/* Driver Code */
fun main() {
/* 連結リストを初期化 */
// 各ノードを初期化
val n0 = ListNode(1)
val n1 = ListNode(3)
val n2 = ListNode(2)
val n3 = ListNode(5)
val n4 = ListNode(4)
// ノード間の参照を構築する
n0.next = n1
n1.next = n2
n2.next = n3
n3.next = n4
println("初期化した連結リストは")
printLinkedList(n0)
/* ノードを挿入 */
insert(n0, ListNode(0))
println("ノード挿入後の連結リストは")
printLinkedList(n0)
/* ノードを削除 */
remove(n0)
println("ノード削除後の連結リストは")
printLinkedList(n0)
/* ノードにアクセス */
val node = access(n0, 3)!!
println("連結リストのインデックス 3 にあるノードの値 = ${node._val}")
/* ノードを探索 */
val index = find(n0, 2)
println("連結リスト内で値が 2 のノードのインデックス = $index")
}
@@ -0,0 +1,63 @@
/**
* File: list.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_array_and_linkedlist
/* Driver Code */
fun main() {
/* リストを初期化 */
// 可変集合
val nums = mutableListOf(1, 3, 2, 5, 4)
println("リスト nums = $nums")
/* 要素にアクセス */
val num = nums[1]
println("インデックス 1 の要素にアクセスし,num = $num")
/* 要素を更新 */
nums[1] = 0
println("インデックス 1 の要素を 0 に更新し,nums = $nums")
/* リストを空にする */
nums.clear()
println("リストを空にした後の nums = $nums")
/* 末尾に要素を追加 */
nums.add(1)
nums.add(3)
nums.add(2)
nums.add(5)
nums.add(4)
println("要素追加後の nums = $nums")
/* 中間に要素を挿入 */
nums.add(3, 6)
println("インデックス 3 に数値 6 を挿入し,nums = $nums")
/* 要素を削除 */
nums.removeAt(3)
println("インデックス 3 の要素を削除し,nums = $nums")
/* インデックスでリストを走査 */
var count = 0
for (i in nums.indices) {
count += nums[i]
}
/* リスト要素を直接走査 */
for (j in nums) {
count += j
}
/* 2 つのリストを連結する */
val nums1 = mutableListOf(6, 8, 7, 10, 9)
nums.addAll(nums1)
println("リスト nums1 を nums の後ろに連結し,nums = $nums")
/* リストをソート */
nums.sort()
println("リストをソートした後の nums = $nums")
}
@@ -0,0 +1,139 @@
/**
* File: my_list.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_array_and_linkedlist
/* リストクラス */
class MyList {
private var arr: IntArray = intArrayOf() // 配列(リスト要素を格納)
private var capacity: Int = 10 // リスト容量
private var size: Int = 0 // リストの長さ(現在の要素数)
private var extendRatio: Int = 2 // リスト拡張時の増加倍率
/* コンストラクタ */
init {
arr = IntArray(capacity)
}
/* リストの長さを取得(現在の要素数) */
fun size(): Int {
return size
}
/* リスト容量を取得する */
fun capacity(): Int {
return capacity
}
/* 要素にアクセス */
fun get(index: Int): Int {
// インデックスが範囲外なら例外を送出する。以下同様
if (index < 0 || index >= size)
throw IndexOutOfBoundsException("インデックスが範囲外")
return arr[index]
}
/* 要素を更新 */
fun set(index: Int, num: Int) {
if (index < 0 || index >= size)
throw IndexOutOfBoundsException("インデックスが範囲外")
arr[index] = num
}
/* 末尾に要素を追加 */
fun add(num: Int) {
// 要素数が容量を超えると、拡張機構が発動する
if (size == capacity())
extendCapacity()
arr[size] = num
// 要素数を更新
size++
}
/* 中間に要素を挿入 */
fun insert(index: Int, num: Int) {
if (index < 0 || index >= size)
throw IndexOutOfBoundsException("インデックスが範囲外")
// 要素数が容量を超えると、拡張機構が発動する
if (size == capacity())
extendCapacity()
// index 以降の要素をすべて 1 つ後ろへずらす
for (j in size - 1 downTo index)
arr[j + 1] = arr[j]
arr[index] = num
// 要素数を更新
size++
}
/* 要素を削除 */
fun remove(index: Int): Int {
if (index < 0 || index >= size)
throw IndexOutOfBoundsException("インデックスが範囲外")
val num = arr[index]
// インデックス index より後の要素をすべて 1 つ前に移動する
for (j in index..<size - 1)
arr[j] = arr[j + 1]
// 要素数を更新
size--
// 削除された要素を返す
return num
}
/* リストの拡張 */
fun extendCapacity() {
// 元の配列の extendRatio 倍の長さを持つ新しい配列を作成し、元の配列をコピーする
arr = arr.copyOf(capacity() * extendRatio)
// リストの容量を更新
capacity = arr.size
}
/* リストを配列に変換する */
fun toArray(): IntArray {
val size = size()
// 有効長の範囲内のリスト要素のみを変換
val arr = IntArray(size)
for (i in 0..<size) {
arr[i] = get(i)
}
return arr
}
}
/* Driver Code */
fun main() {
/* リストを初期化 */
val nums = MyList()
/* 末尾に要素を追加 */
nums.add(1)
nums.add(3)
nums.add(2)
nums.add(5)
nums.add(4)
println("リスト nums = ${nums.toArray().contentToString()},容量 = ${nums.capacity()},長さ = ${nums.size()}")
/* 中間に要素を挿入 */
nums.insert(3, 6)
println("インデックス 3 に数値 6 を挿入し,nums = ${nums.toArray().contentToString()}")
/* 要素を削除 */
nums.remove(3)
println("インデックス 3 の要素を削除し,nums = ${nums.toArray().contentToString()}")
/* 要素にアクセス */
val num = nums.get(1)
println("インデックス 1 の要素にアクセスし,num = $num")
/* 要素を更新 */
nums.set(1, 0)
println("インデックス 1 の要素を 0 に更新し,nums = ${nums.toArray().contentToString()}")
/* 拡張機構をテストする */
for (i in 0..9) {
// i = 5 のとき、リスト長が容量を超えるため、この時点で拡張機構が発動する
nums.add(i)
}
println("拡張後のリスト nums = ${nums.toArray().contentToString()},容量 = ${nums.capacity()},長さ = ${nums.size()}")
}
@@ -0,0 +1,85 @@
/**
* File: n_queens.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_backtracking.n_queens
/* バックトラッキング:N クイーン */
fun backtrack(
row: Int,
n: Int,
state: MutableList<MutableList<String>>,
res: MutableList<MutableList<MutableList<String>>?>,
cols: BooleanArray,
diags1: BooleanArray,
diags2: BooleanArray
) {
// すべての行への配置が完了したら、解を記録する
if (row == n) {
val copyState = mutableListOf<MutableList<String>>()
for (sRow in state) {
copyState.add(sRow.toMutableList())
}
res.add(copyState)
return
}
// すべての列を走査
for (col in 0..<n) {
// このマスに対応する主対角線と副対角線を計算
val diag1 = row - col + n - 1
val diag2 = row + col
// 枝刈り:そのマスの列、主対角線、副対角線にクイーンがあってはならない
if (!cols[col] && !diags1[diag1] && !diags2[diag2]) {
// 試行:そのマスにクイーンを置く
state[row][col] = "Q"
diags2[diag2] = true
diags1[diag1] = diags2[diag2]
cols[col] = diags1[diag1]
// 次の行に配置する
backtrack(row + 1, n, state, res, cols, diags1, diags2)
// 戻す:そのマスを空きマスに戻す
state[row][col] = "#"
diags2[diag2] = false
diags1[diag1] = diags2[diag2]
cols[col] = diags1[diag1]
}
}
}
/* N クイーンを解く */
fun nQueens(n: Int): MutableList<MutableList<MutableList<String>>?> {
// n*n の盤面を初期化する。'Q' はクイーン、'#' は空きマスを表す
val state = mutableListOf<MutableList<String>>()
for (i in 0..<n) {
val row = mutableListOf<String>()
for (j in 0..<n) {
row.add("#")
}
state.add(row)
}
val cols = BooleanArray(n) // 列にクイーンがあるか記録
val diags1 = BooleanArray(2 * n - 1) // 主対角線にクイーンがあるかを記録
val diags2 = BooleanArray(2 * n - 1) // 副対角線にクイーンがあるかを記録
val res = mutableListOf<MutableList<MutableList<String>>?>()
backtrack(0, n, state, res, cols, diags1, diags2)
return res
}
/* Driver Code */
fun main() {
val n = 4
val res = nQueens(n)
println("入力された盤面の縦横は $n")
println("クイーンの配置パターンは全部で ${res.size} 通り")
for (state in res) {
println("--------------------")
for (row in state!!) {
println(row)
}
}
}
@@ -0,0 +1,53 @@
/**
* File: permutations_i.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_backtracking.permutations_i
/* バックトラッキング:順列 I */
fun backtrack(
state: MutableList<Int>,
choices: IntArray,
selected: BooleanArray,
res: MutableList<MutableList<Int>?>
) {
// 状態の長さが要素数に等しければ、解を記録
if (state.size == choices.size) {
res.add(state.toMutableList())
return
}
// すべての選択肢を走査
for (i in choices.indices) {
val choice = choices[i]
// 枝刈り:要素の重複選択を許可しない
if (!selected[i]) {
// 試行: 選択を行い、状態を更新
selected[i] = true
state.add(choice)
// 次の選択へ進む
backtrack(state, choices, selected, res)
// バックトラック:選択を取り消し、前の状態に戻す
selected[i] = false
state.removeAt(state.size - 1)
}
}
}
/* 全順列 I */
fun permutationsI(nums: IntArray): MutableList<MutableList<Int>?> {
val res = mutableListOf<MutableList<Int>?>()
backtrack(mutableListOf(), nums, BooleanArray(nums.size), res)
return res
}
/* Driver Code */
fun main() {
val nums = intArrayOf(1, 2, 3)
val res = permutationsI(nums)
println("入力配列 nums = ${nums.contentToString()}")
println("すべての順列 res = $res")
}
@@ -0,0 +1,54 @@
/**
* File: permutations_ii.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_backtracking.permutations_ii
/* バックトラッキング:順列 II */
fun backtrack(
state: MutableList<Int>,
choices: IntArray,
selected: BooleanArray,
res: MutableList<MutableList<Int>?>
) {
// 状態の長さが要素数に等しければ、解を記録
if (state.size == choices.size) {
res.add(state.toMutableList())
return
}
// すべての選択肢を走査
val duplicated = HashSet<Int>()
for (i in choices.indices) {
val choice = choices[i]
// 枝刈り:要素の重複選択を許可せず、同値要素の重複選択も許可しない
if (!selected[i] && !duplicated.contains(choice)) {
// 試行: 選択を行い、状態を更新
duplicated.add(choice) // 選択済みの要素値を記録
selected[i] = true
state.add(choice)
// 次の選択へ進む
backtrack(state, choices, selected, res)
// バックトラック:選択を取り消し、前の状態に戻す
selected[i] = false
state.removeAt(state.size - 1)
}
}
}
/* 全順列 II */
fun permutationsII(nums: IntArray): MutableList<MutableList<Int>?> {
val res = mutableListOf<MutableList<Int>?>()
backtrack(mutableListOf(), nums, BooleanArray(nums.size), res)
return res
}
/* Driver Code */
fun main() {
val nums = intArrayOf(1, 2, 2)
val res = permutationsII(nums)
println("入力配列 nums = ${nums.contentToString()}")
println("すべての順列 res = $res")
}
@@ -0,0 +1,43 @@
/**
* File: preorder_traversal_i_compact.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_backtracking.preorder_traversal_i_compact
import utils.TreeNode
import utils.printTree
var res: MutableList<TreeNode>? = null
/* 前順走査:例題 1 */
fun preOrder(root: TreeNode?) {
if (root == null) {
return
}
if (root._val == 7) {
// 解を記録
res!!.add(root)
}
preOrder(root.left)
preOrder(root.right)
}
/* Driver Code */
fun main() {
val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7))
println("\n二分木を初期化")
printTree(root)
// 先行順走査
res = mutableListOf()
preOrder(root)
println("\n値が 7 のノードをすべて出力")
val vals = mutableListOf<Int>()
for (node in res!!) {
vals.add(node._val)
}
println(vals)
}
@@ -0,0 +1,51 @@
/**
* File: preorder_traversal_ii_compact.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_backtracking.preorder_traversal_ii_compact
import utils.TreeNode
import utils.printTree
var path: MutableList<TreeNode>? = null
var res: MutableList<MutableList<TreeNode>>? = null
/* 前順走査:例題 2 */
fun preOrder(root: TreeNode?) {
if (root == null) {
return
}
// 試す
path!!.add(root)
if (root._val == 7) {
// 解を記録
res!!.add(path!!.toMutableList())
}
preOrder(root.left)
preOrder(root.right)
// バックトラック
path!!.removeAt(path!!.size - 1)
}
/* Driver Code */
fun main() {
val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7))
println("\n二分木を初期化")
printTree(root)
// 先行順走査
path = mutableListOf()
res = mutableListOf()
preOrder(root)
println("\n根ノードからノード 7 までのすべての経路を出力")
for (path in res!!) {
val _vals = mutableListOf<Int>()
for (node in path) {
_vals.add(node._val)
}
println(_vals)
}
}
@@ -0,0 +1,52 @@
/**
* File: preorder_traversal_iii_compact.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_backtracking.preorder_traversal_iii_compact
import utils.TreeNode
import utils.printTree
var path: MutableList<TreeNode>? = null
var res: MutableList<MutableList<TreeNode>>? = null
/* 前順走査:例題 3 */
fun preOrder(root: TreeNode?) {
// 枝刈り
if (root == null || root._val == 3) {
return
}
// 試す
path!!.add(root)
if (root._val == 7) {
// 解を記録
res!!.add(path!!.toMutableList())
}
preOrder(root.left)
preOrder(root.right)
// バックトラック
path!!.removeAt(path!!.size - 1)
}
/* Driver Code */
fun main() {
val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7))
println("\n二分木を初期化")
printTree(root)
// 先行順走査
path = mutableListOf()
res = mutableListOf()
preOrder(root)
println("\n根ノードからノード 7 までのすべての経路を出力し,経路には値が 3 のノードを含めない")
for (path in res!!) {
val _vals = mutableListOf<Int>()
for (node in path) {
_vals.add(node._val)
}
println(_vals)
}
}
@@ -0,0 +1,82 @@
/**
* File: preorder_traversal_iii_template.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_backtracking.preorder_traversal_iii_template
import utils.TreeNode
import utils.printTree
/* 現在の状態が解かどうかを判定 */
fun isSolution(state: MutableList<TreeNode?>): Boolean {
return state.isNotEmpty() && state[state.size - 1]?._val == 7
}
/* 解を記録 */
fun recordSolution(state: MutableList<TreeNode?>?, res: MutableList<MutableList<TreeNode?>?>) {
res.add(state!!.toMutableList())
}
/* 現在の状態で、この選択が有効かどうかを判定 */
fun isValid(state: MutableList<TreeNode?>?, choice: TreeNode?): Boolean {
return choice != null && choice._val != 3
}
/* 状態を更新 */
fun makeChoice(state: MutableList<TreeNode?>, choice: TreeNode?) {
state.add(choice)
}
/* 状態を元に戻す */
fun undoChoice(state: MutableList<TreeNode?>, choice: TreeNode?) {
state.removeLast()
}
/* バックトラッキング:例題 3 */
fun backtrack(
state: MutableList<TreeNode?>,
choices: MutableList<TreeNode?>,
res: MutableList<MutableList<TreeNode?>?>
) {
// 解かどうかを確認
if (isSolution(state)) {
// 解を記録
recordSolution(state, res)
}
// すべての選択肢を走査
for (choice in choices) {
// 枝刈り:選択が妥当かを確認する
if (isValid(state, choice)) {
// 試行: 選択を行い、状態を更新
makeChoice(state, choice)
// 次の選択へ進む
backtrack(state, mutableListOf(choice!!.left, choice.right), res)
// バックトラック:選択を取り消し、前の状態に戻す
undoChoice(state, choice)
}
}
}
/* Driver Code */
fun main() {
val root = TreeNode.listToTree(mutableListOf(1, 7, 3, 4, 5, 6, 7))
println("\n二分木を初期化")
printTree(root)
// バックトラッキング法
val res = mutableListOf<MutableList<TreeNode?>?>()
backtrack(mutableListOf(), mutableListOf(root), res)
println("\n根ノードからノード 7 までのすべての経路を出力し,経路に値が 3 のノードを含まないことを条件とする")
for (path in res) {
val vals = mutableListOf<Int>()
for (node in path!!) {
if (node != null) {
vals.add(node._val)
}
}
println(vals)
}
}
@@ -0,0 +1,58 @@
/**
* File: subset_sum_i.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_backtracking.subset_sum_i
/* バックトラッキング:部分和 I */
fun backtrack(
state: MutableList<Int>,
target: Int,
choices: IntArray,
start: Int,
res: MutableList<MutableList<Int>?>
) {
// 部分集合の和が target に等しければ、解を記録
if (target == 0) {
res.add(state.toMutableList())
return
}
// すべての選択肢を走査
// 枝刈り 2: start から走査し、重複する部分集合の生成を避ける
for (i in start..<choices.size) {
// 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する
// 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため
if (target - choices[i] < 0) {
break
}
// 試す:選択を行い、target と start を更新
state.add(choices[i])
// 次の選択へ進む
backtrack(state, target - choices[i], choices, i, res)
// バックトラック:選択を取り消し、前の状態に戻す
state.removeAt(state.size - 1)
}
}
/* 部分和 I を解く */
fun subsetSumI(nums: IntArray, target: Int): MutableList<MutableList<Int>?> {
val state = mutableListOf<Int>() // 状態(部分集合)
nums.sort() // nums をソート
val start = 0 // 開始点を走査
val res = mutableListOf<MutableList<Int>?>() // 結果リスト(部分集合のリスト)
backtrack(state, target, nums, start, res)
return res
}
/* Driver Code */
fun main() {
val nums = intArrayOf(3, 4, 5)
val target = 9
val res = subsetSumI(nums, target)
println("入力配列 nums = ${nums.contentToString()}, target = $target")
println("和が $target に等しいすべての部分集合 res = $res")
}
@@ -0,0 +1,55 @@
/**
* File: subset_sum_i_native.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_backtracking.subset_sum_i_naive
/* バックトラッキング:部分和 I */
fun backtrack(
state: MutableList<Int>,
target: Int,
total: Int,
choices: IntArray,
res: MutableList<MutableList<Int>?>
) {
// 部分集合の和が target に等しければ、解を記録
if (total == target) {
res.add(state.toMutableList())
return
}
// すべての選択肢を走査
for (i in choices.indices) {
// 枝刈り:部分和が target を超える場合はその選択をスキップする
if (total + choices[i] > target) {
continue
}
// 試行:選択を行い、要素と total を更新する
state.add(choices[i])
// 次の選択へ進む
backtrack(state, target, total + choices[i], choices, res)
// バックトラック:選択を取り消し、前の状態に戻す
state.removeAt(state.size - 1)
}
}
/* 部分和 I を解く(重複部分集合を含む) */
fun subsetSumINaive(nums: IntArray, target: Int): MutableList<MutableList<Int>?> {
val state = mutableListOf<Int>() // 状態(部分集合)
val total = 0 // 部分和
val res = mutableListOf<MutableList<Int>?>() // 結果リスト(部分集合のリスト)
backtrack(state, target, total, nums, res)
return res
}
/* Driver Code */
fun main() {
val nums = intArrayOf(3, 4, 5)
val target = 9
val res = subsetSumINaive(nums, target)
println("入力配列 nums = ${nums.contentToString()}, target = $target")
println("和が $target に等しいすべての部分集合 res = $res")
println("この方法の出力結果には重複した集合が含まれるので注意")
}
@@ -0,0 +1,62 @@
/**
* File: subset_sum_ii.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_backtracking.subset_sum_ii
/* バックトラッキング:部分和 II */
fun backtrack(
state: MutableList<Int>,
target: Int,
choices: IntArray,
start: Int,
res: MutableList<MutableList<Int>?>
) {
// 部分集合の和が target に等しければ、解を記録
if (target == 0) {
res.add(state.toMutableList())
return
}
// すべての選択肢を走査
// 枝刈り 2: start から走査し、重複する部分集合の生成を避ける
// 枝刈り 3: start から走査し、同じ要素の重複選択を避ける
for (i in start..<choices.size) {
// 枝刈り1:部分集合の和が target を超えたら、直ちにループを終了する
// 配列はソート済みで後続要素のほうが大きく、部分集合の和は必ず target を超えるため
if (target - choices[i] < 0) {
break
}
// 枝刈り4:この要素が左隣の要素と等しければ、その探索分岐は重複しているためスキップする
if (i > start && choices[i] == choices[i - 1]) {
continue
}
// 試す:選択を行い、target と start を更新
state.add(choices[i])
// 次の選択へ進む
backtrack(state, target - choices[i], choices, i + 1, res)
// バックトラック:選択を取り消し、前の状態に戻す
state.removeAt(state.size - 1)
}
}
/* 部分和 II を解く */
fun subsetSumII(nums: IntArray, target: Int): MutableList<MutableList<Int>?> {
val state = mutableListOf<Int>() // 状態(部分集合)
nums.sort() // nums をソート
val start = 0 // 開始点を走査
val res = mutableListOf<MutableList<Int>?>() // 結果リスト(部分集合のリスト)
backtrack(state, target, nums, start, res)
return res
}
/* Driver Code */
fun main() {
val nums = intArrayOf(4, 4, 5)
val target = 9
val res = subsetSumII(nums, target)
println("入力配列 nums = ${nums.contentToString()}, target = $target")
println("和が $target に等しいすべての部分集合 res = $res")
}
@@ -0,0 +1,74 @@
/**
* File: iteration.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_computational_complexity.iteration
/* for ループ */
fun forLoop(n: Int): Int {
var res = 0
// 1, 2, ..., n-1, n を順に加算する
for (i in 1..n) {
res += i
}
return res
}
/* while ループ */
fun whileLoop(n: Int): Int {
var res = 0
var i = 1 // 条件変数を初期化する
// 1, 2, ..., n-1, n を順に加算する
while (i <= n) {
res += i
i++ // 条件変数を更新する
}
return res
}
/* while ループ(2回更新) */
fun whileLoopII(n: Int): Int {
var res = 0
var i = 1 // 条件変数を初期化する
// 1, 4, 10, ... を順に加算する
while (i <= n) {
res += i
// 条件変数を更新する
i++
i *= 2
}
return res
}
/* 二重 for ループ */
fun nestedForLoop(n: Int): String {
val res = StringBuilder()
// i = 1, 2, ..., n-1, n とループする
for (i in 1..n) {
// j = 1, 2, ..., n-1, n とループする
for (j in 1..n) {
res.append(" ($i, $j), ")
}
}
return res.toString()
}
/* Driver Code */
fun main() {
val n = 5
var res: Int
res = forLoop(n)
println("\nfor ループの合計結果 res = $res")
res = whileLoop(n)
println("\nwhile ループの合計結果 res = $res")
res = whileLoopII(n)
println("\nwhile ループ (2 回更新) の合計結果 res = $res")
val resStr = nestedForLoop(n)
println("\n二重 for ループの走査結果 $resStr")
}
@@ -0,0 +1,78 @@
/**
* File: recursion.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_computational_complexity.recursion
import java.util.*
/* 再帰 */
fun recur(n: Int): Int {
// 終了条件
if (n == 1)
return 1
// 再帰: 再帰呼び出し
val res = recur(n - 1)
// 戻る: 結果を返す
return n + res
}
/* 反復で再帰を模擬する */
fun forLoopRecur(n: Int): Int {
// 明示的なスタックを使ってシステムコールスタックを模擬する
val stack = Stack<Int>()
var res = 0
// 再帰: 再帰呼び出し
for (i in n downTo 0) {
// 「スタックへのプッシュ」で「再帰」を模擬する
stack.push(i)
}
// 戻る: 結果を返す
while (stack.isNotEmpty()) {
// 「スタックから取り出す操作」で「帰り」をシミュレート
res += stack.pop()
}
// res = 1+2+3+...+n
return res
}
/* 末尾再帰 */
tailrec fun tailRecur(n: Int, res: Int): Int {
// `tailrec` キーワードを追加して末尾再帰最適化を有効にする
// 終了条件
if (n == 0)
return res
// 末尾再帰呼び出し
return tailRecur(n - 1, res + n)
}
/* フィボナッチ数列:再帰 */
fun fib(n: Int): Int {
// 終了条件 f(1) = 0, f(2) = 1
if (n == 1 || n == 2)
return n - 1
// f(n) = f(n-1) + f(n-2) を再帰的に呼び出す
val res = fib(n - 1) + fib(n - 2)
// 結果 f(n) を返す
return res
}
/* Driver Code */
fun main() {
val n = 5
var res: Int
res = recur(n)
println("\n再帰関数の合計結果 res = $res")
res = forLoopRecur(n)
println("\n反復で再帰をシミュレートした合計結果 res = $res")
res = tailRecur(n, 0)
println("\n末尾再帰関数の合計結果 res = $res")
res = fib(n)
println("\nフィボナッチ数列の第 $n 項は $res")
}
@@ -0,0 +1,109 @@
/**
* File: space_complexity.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_computational_complexity.space_complexity
import utils.ListNode
import utils.TreeNode
import utils.printTree
/* 関数 */
fun function(): Int {
// 何らかの処理を行う
return 0
}
/* 定数階 */
fun constant(n: Int) {
// 定数、変数、オブジェクトは O(1) の空間を占める
val a = 0
var b = 0
val nums = Array(10000) { 0 }
val node = ListNode(0)
// ループ内の変数は O(1) の空間を占める
for (i in 0..<n) {
val c = 0
}
// ループ内の関数は O(1) の空間を占める
for (i in 0..<n) {
function()
}
}
/* 線形階 */
fun linear(n: Int) {
// 長さ n の配列は O(n) の空間を使用
val nums = Array(n) { 0 }
// 長さ n のリストは O(n) の空間を使用
val nodes = mutableListOf<ListNode>()
for (i in 0..<n) {
nodes.add(ListNode(i))
}
// 長さ n のハッシュテーブルは O(n) の空間を使用
val map = mutableMapOf<Int, String>()
for (i in 0..<n) {
map[i] = i.toString()
}
}
/* 線形時間(再帰実装) */
fun linearRecur(n: Int) {
println("再帰 n = $n")
if (n == 1)
return
linearRecur(n - 1)
}
/* 二乗階 */
fun quadratic(n: Int) {
// 行列は O(n^2) の空間を使用する
val numMatrix = arrayOfNulls<Array<Int>?>(n)
// 二次元リストは O(n^2) の空間を使用
val numList = mutableListOf<MutableList<Int>>()
for (i in 0..<n) {
val tmp = mutableListOf<Int>()
for (j in 0..<n) {
tmp.add(0)
}
numList.add(tmp)
}
}
/* 二次時間(再帰実装) */
tailrec fun quadraticRecur(n: Int): Int {
if (n <= 0)
return 0
// 配列 nums の長さは n, n-1, ..., 2, 1
val nums = Array(n) { 0 }
println("再帰 n = $n における nums の長さ = ${nums.size}")
return quadraticRecur(n - 1)
}
/* 指数時間(完全二分木の構築) */
fun buildTree(n: Int): TreeNode? {
if (n == 0)
return null
val root = TreeNode(0)
root.left = buildTree(n - 1)
root.right = buildTree(n - 1)
return root
}
/* Driver Code */
fun main() {
val n = 5
// 定数階
constant(n)
// 線形階
linear(n)
linearRecur(n)
// 二乗階
quadratic(n)
quadraticRecur(n)
// 指数オーダー
val root = buildTree(n)
printTree(root)
}
@@ -0,0 +1,168 @@
/**
* File: time_complexity.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_computational_complexity.time_complexity
/* 定数階 */
fun constant(n: Int): Int {
var count = 0
val size = 100000
for (i in 0..<size)
count++
return count
}
/* 線形階 */
fun linear(n: Int): Int {
var count = 0
for (i in 0..<n)
count++
return count
}
/* 線形時間(配列を走査) */
fun arrayTraversal(nums: IntArray): Int {
var count = 0
// ループ回数は配列長に比例する
for (num in nums) {
count++
}
return count
}
/* 二乗階 */
fun quadratic(n: Int): Int {
var count = 0
// ループ回数はデータサイズ n の二乗に比例する
for (i in 0..<n) {
for (j in 0..<n) {
count++
}
}
return count
}
/* 二次時間(バブルソート) */
fun bubbleSort(nums: IntArray): Int {
var count = 0 // カウンタ
// 外側のループ:未ソート区間は [0, i]
for (i in nums.size - 1 downTo 1) {
// 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
for (j in 0..<i) {
if (nums[j] > nums[j + 1]) {
// nums[j] と nums[j + 1] を交換
val temp = nums[j]
nums[j] = nums[j + 1]
nums[j + 1] = temp
count += 3 // 要素交換には 3 回の単位操作が含まれる
}
}
}
return count
}
/* 指数時間(ループ実装) */
fun exponential(n: Int): Int {
var count = 0
var base = 1
// 細胞は各ラウンドで 2 つに分裂し、数列 1, 2, 4, 8, ..., 2^(n-1) を形成する
for (i in 0..<n) {
for (j in 0..<base) {
count++
}
base *= 2
}
// count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1
return count
}
/* 指数時間(再帰実装) */
fun expRecur(n: Int): Int {
if (n == 1) {
return 1
}
return expRecur(n - 1) + expRecur(n - 1) + 1
}
/* 対数時間(ループ実装) */
fun logarithmic(n: Int): Int {
var n1 = n
var count = 0
while (n1 > 1) {
n1 /= 2
count++
}
return count
}
/* 対数時間(再帰実装) */
fun logRecur(n: Int): Int {
if (n <= 1)
return 0
return logRecur(n / 2) + 1
}
/* 線形対数時間 */
fun linearLogRecur(n: Int): Int {
if (n <= 1)
return 1
var count = linearLogRecur(n / 2) + linearLogRecur(n / 2)
for (i in 0..<n) {
count++
}
return count
}
/* 階乗時間(再帰実装) */
fun factorialRecur(n: Int): Int {
if (n == 0)
return 1
var count = 0
// 1個から n 個に分裂
for (i in 0..<n) {
count += factorialRecur(n - 1)
}
return count
}
/* Driver Code */
fun main() {
// n を変えて実行し、各計算量で操作回数がどう変化するかを確認できる
val n = 8
println("入力データサイズ n = $n")
var count = constant(n)
println("定数時間の操作回数 = $count")
count = linear(n)
println("線形時間の操作回数 = $count")
count = arrayTraversal(IntArray(n))
println("線形時間(配列走査)の操作回数 = $count")
count = quadratic(n)
println("二乗時間の操作回数 = $count")
val nums = IntArray(n)
for (i in 0..<n)
nums[i] = n - i // [n,n-1,...,2,1]
count = bubbleSort(nums)
println("二乗時間(バブルソート)の操作回数 = $count")
count = exponential(n)
println("指数時間(ループ実装)の操作回数 = $count")
count = expRecur(n)
println("指数時間(再帰実装)の操作回数 = $count")
count = logarithmic(n)
println("対数時間(ループ実装)の操作回数 = $count")
count = logRecur(n)
println("対数階(再帰実装)の操作回数 = $count")
count = linearLogRecur(n)
println("線形対数階(再帰実装)の操作回数 = $count")
count = factorialRecur(n)
println("階乗階(再帰実装)の操作回数 = $count")
}
@@ -0,0 +1,45 @@
/**
* File: worst_best_time_complexity.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_computational_complexity.worst_best_time_complexity
/* 要素が { 1, 2, ..., n } で、順序がシャッフルされた配列を生成 */
fun randomNumbers(n: Int): Array<Int?> {
val nums = IntArray(n)
// 配列 nums = { 1, 2, 3, ..., n } を生成
for (i in 0..<n) {
nums[i] = i + 1
}
// 配列要素をランダムにシャッフル
nums.shuffle()
val res = arrayOfNulls<Int>(n)
for (i in 0..<n) {
res[i] = nums[i]
}
return res
}
/* 配列 nums 内で数値 1 のインデックスを探す */
fun findOne(nums: Array<Int?>): Int {
for (i in nums.indices) {
// 要素 1 が配列の先頭にあるとき、最良時間計算量 O(1) となる
// 要素 1 が配列の末尾にあるとき、最悪時間計算量 O(n) となる
if (nums[i] == 1)
return i
}
return -1
}
/* Driver Code */
fun main() {
for (i in 0..9) {
val n = 100
val nums = randomNumbers(n)
val index = findOne(nums)
println("\n配列 [ 1, 2, ..., n ] をシャッフルした後 = ${nums.contentToString()}")
println("数字 1 のインデックスは $index")
}
}
@@ -0,0 +1,49 @@
/**
* File: binary_search_recur.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_divide_and_conquer.binary_search_recur
/* 二分探索:問題 f(i, j) */
fun dfs(
nums: IntArray,
target: Int,
i: Int,
j: Int
): Int {
// 区間が空なら対象要素は存在しないので -1 を返す
if (i > j) {
return -1
}
// 中点インデックス m を計算
val m = (i + j) / 2
return if (nums[m] < target) {
// 部分問題 f(m+1, j) を再帰的に解く
dfs(nums, target, m + 1, j)
} else if (nums[m] > target) {
// 部分問題 f(i, m-1) を再帰的に解く
dfs(nums, target, i, m - 1)
} else {
// 目標要素が見つかったらそのインデックスを返す
m
}
}
/* 二分探索 */
fun binarySearch(nums: IntArray, target: Int): Int {
val n = nums.size
// 問題 f(0, n-1) を解く
return dfs(nums, target, 0, n - 1)
}
/* Driver Code */
fun main() {
val target = 6
val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35)
// 二分探索(両閉区間)
val index = binarySearch(nums, target)
println("対象要素 6 のインデックス = $index")
}
@@ -0,0 +1,55 @@
/**
* File: build_tree.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_divide_and_conquer.build_tree
import utils.TreeNode
import utils.printTree
/* 二分木を構築:分割統治 */
fun dfs(
preorder: IntArray,
inorderMap: Map<Int?, Int?>,
i: Int,
l: Int,
r: Int
): TreeNode? {
// 部分木区間が空なら終了する
if (r - l < 0) return null
// ルートノードを初期化する
val root = TreeNode(preorder[i])
// m を求めて左右部分木を分割する
val m = inorderMap[preorder[i]]!!
// 部分問題:左部分木を構築する
root.left = dfs(preorder, inorderMap, i + 1, l, m - 1)
// 部分問題:右部分木を構築する
root.right = dfs(preorder, inorderMap, i + 1 + m - l, m + 1, r)
// 根ノードを返す
return root
}
/* 二分木を構築 */
fun buildTree(preorder: IntArray, inorder: IntArray): TreeNode? {
// inorder の要素からインデックスへの対応を格納するハッシュテーブルを初期化する
val inorderMap = HashMap<Int?, Int?>()
for (i in inorder.indices) {
inorderMap[inorder[i]] = i
}
val root = dfs(preorder, inorderMap, 0, 0, inorder.size - 1)
return root
}
/* Driver Code */
fun main() {
val preorder = intArrayOf(3, 9, 2, 1, 7)
val inorder = intArrayOf(9, 3, 1, 2, 7)
println("前順走査 = ${preorder.contentToString()}")
println("中順走査 = ${inorder.contentToString()}")
val root = buildTree(preorder, inorder)
println("構築した二分木:")
printTree(root)
}
@@ -0,0 +1,56 @@
/**
* File: hanota.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_divide_and_conquer.hanota
/* 円盤を 1 枚移動 */
fun move(src: MutableList<Int>, tar: MutableList<Int>) {
// src の上から円盤を1枚取り出す
val pan = src.removeAt(src.size - 1)
// 円盤を tar の上に置く
tar.add(pan)
}
/* ハノイの塔の問題 f(i) を解く */
fun dfs(i: Int, src: MutableList<Int>, buf: MutableList<Int>, tar: MutableList<Int>) {
// src に円盤が 1 枚だけ残っている場合は、そのまま tar へ移す
if (i == 1) {
move(src, tar)
return
}
// 部分問題 f(i-1)src の上部 i-1 枚の円盤を tar を補助にして buf へ移す
dfs(i - 1, src, tar, buf)
// 部分問題 f(1):src に残る 1 枚の円盤を tar に移す
move(src, tar)
// 部分問題 f(i-1)buf の上部 i-1 枚の円盤を src を補助にして tar へ移す
dfs(i - 1, buf, src, tar)
}
/* ハノイの塔を解く */
fun solveHanota(A: MutableList<Int>, B: MutableList<Int>, C: MutableList<Int>) {
val n = A.size
// A の上から n 枚の円盤を B を介して C へ移す
dfs(n, A, B, C)
}
/* Driver Code */
fun main() {
// リスト末尾が柱の頂上
val A = mutableListOf(5, 4, 3, 2, 1)
val B = mutableListOf<Int>()
val C = mutableListOf<Int>()
println("初期状態:")
println("A = $A")
println("B = $B")
println("C = $C")
solveHanota(A, B, C)
println("円盤の移動完了後:")
println("A = $A")
println("B = $B")
println("C = $C")
}
@@ -0,0 +1,45 @@
/**
* File: climbing_stairs_backtrack.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_dynamic_programming
/* バックトラッキング */
fun backtrack(
choices: MutableList<Int>,
state: Int,
n: Int,
res: MutableList<Int>
) {
// 第 n 段に到達したら、方法数を 1 増やす
if (state == n)
res[0] = res[0] + 1
// すべての選択肢を走査
for (choice in choices) {
// 枝刈り: 第 n 段を超えないようにする
if (state + choice > n) continue
// 試行: 選択を行い、状態を更新
backtrack(choices, state + choice, n, res)
// バックトラック
}
}
/* 階段登り:バックトラッキング */
fun climbingStairsBacktrack(n: Int): Int {
val choices = mutableListOf(1, 2) // 1 段または 2 段上ることを選べる
val state = 0 // 第 0 段から上り始める
val res = mutableListOf<Int>()
res.add(0) // res[0] を使って方法数を記録する
backtrack(choices, state, n, res)
return res[0]
}
/* Driver Code */
fun main() {
val n = 9
val res = climbingStairsBacktrack(n)
println("$n 段の階段の登り方は全部で $res 通り")
}
@@ -0,0 +1,35 @@
/**
* File: climbing_stairs_constraint_dp.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_dynamic_programming
/* 制約付き階段登り:動的計画法 */
fun climbingStairsConstraintDP(n: Int): Int {
if (n == 1 || n == 2) {
return 1
}
// 部分問題の解を保存するために dp テーブルを初期化
val dp = Array(n + 1) { IntArray(3) }
// 初期状態:最小部分問題の解をあらかじめ設定
dp[1][1] = 1
dp[1][2] = 0
dp[2][1] = 0
dp[2][2] = 1
// 状態遷移:小さい部分問題から大きい部分問題へ順に解く
for (i in 3..n) {
dp[i][1] = dp[i - 1][2]
dp[i][2] = dp[i - 2][1] + dp[i - 2][2]
}
return dp[n][1] + dp[n][2]
}
/* Driver Code */
fun main() {
val n = 9
val res = climbingStairsConstraintDP(n)
println("$n 段の階段の登り方は全部で $res 通り")
}
@@ -0,0 +1,29 @@
/**
* File: climbing_stairs_dfs.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_dynamic_programming
/* 検索 */
fun dfs(i: Int): Int {
// dp[1] と dp[2] は既知なので返す
if (i == 1 || i == 2) return i
// dp[i] = dp[i-1] + dp[i-2]
val count = dfs(i - 1) + dfs(i - 2)
return count
}
/* 階段登り:探索 */
fun climbingStairsDFS(n: Int): Int {
return dfs(n)
}
/* Driver Code */
fun main() {
val n = 9
val res = climbingStairsDFS(n)
println("$n 段の階段の登り方は全部で $res 通り")
}
@@ -0,0 +1,36 @@
/**
* File: climbing_stairs_dfs_mem.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_dynamic_programming
/* メモ化探索 */
fun dfs(i: Int, mem: IntArray): Int {
// dp[1] と dp[2] は既知なので返す
if (i == 1 || i == 2) return i
// dp[i] の記録があれば、それをそのまま返す
if (mem[i] != -1) return mem[i]
// dp[i] = dp[i-1] + dp[i-2]
val count = dfs(i - 1, mem) + dfs(i - 2, mem)
// dp[i] を記録する
mem[i] = count
return count
}
/* 階段登り:メモ化探索 */
fun climbingStairsDFSMem(n: Int): Int {
// mem[i] は第 i 段まで上る方法の総数を記録し、-1 は未記録を表す
val mem = IntArray(n + 1)
mem.fill(-1)
return dfs(n, mem)
}
/* Driver Code */
fun main() {
val n = 9
val res = climbingStairsDFSMem(n)
println("$n 段の階段の登り方は全部で $res 通り")
}
@@ -0,0 +1,46 @@
/**
* File: climbing_stairs_dp.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_dynamic_programming
/* 階段登り:動的計画法 */
fun climbingStairsDP(n: Int): Int {
if (n == 1 || n == 2) return n
// 部分問題の解を保存するために dp テーブルを初期化
val dp = IntArray(n + 1)
// 初期状態:最小部分問題の解をあらかじめ設定
dp[1] = 1
dp[2] = 2
// 状態遷移:小さい部分問題から大きい部分問題へ順に解く
for (i in 3..n) {
dp[i] = dp[i - 1] + dp[i - 2]
}
return dp[n]
}
/* 階段登り:空間最適化した動的計画法 */
fun climbingStairsDPComp(n: Int): Int {
if (n == 1 || n == 2) return n
var a = 1
var b = 2
for (i in 3..n) {
val temp = b
b += a
a = temp
}
return b
}
/* Driver Code */
fun main() {
val n = 9
var res = climbingStairsDP(n)
println("$n 段の階段の登り方は全部で $res 通り")
res = climbingStairsDPComp(n)
println("$n 段の階段の登り方は全部で $res 通り")
}
@@ -0,0 +1,71 @@
/**
* File: coin_change.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_dynamic_programming
import kotlin.math.min
/* コイン両替:動的計画法 */
fun coinChangeDP(coins: IntArray, amt: Int): Int {
val n = coins.size
val MAX = amt + 1
// dp テーブルを初期化
val dp = Array(n + 1) { IntArray(amt + 1) }
// 状態遷移:先頭行と先頭列
for (a in 1..amt) {
dp[0][a] = MAX
}
// 状態遷移: 残りの行と列
for (i in 1..n) {
for (a in 1..amt) {
if (coins[i - 1] > a) {
// 目標金額を超えるなら硬貨 i は選ばない
dp[i][a] = dp[i - 1][a]
} else {
// 硬貨 i を選ばない場合と選ぶ場合の小さい方
dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1)
}
}
}
return if (dp[n][amt] != MAX) dp[n][amt] else -1
}
/* コイン交換:空間最適化後の動的計画法 */
fun coinChangeDPComp(coins: IntArray, amt: Int): Int {
val n = coins.size
val MAX = amt + 1
// dp テーブルを初期化
val dp = IntArray(amt + 1)
dp.fill(MAX)
dp[0] = 0
// 状態遷移
for (i in 1..n) {
for (a in 1..amt) {
if (coins[i - 1] > a) {
// 目標金額を超えるなら硬貨 i は選ばない
dp[a] = dp[a]
} else {
// 硬貨 i を選ばない場合と選ぶ場合の小さい方
dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1)
}
}
}
return if (dp[amt] != MAX) dp[amt] else -1
}
/* Driver Code */
fun main() {
val coins = intArrayOf(1, 2, 5)
val amt = 4
// 動的計画法
var res = coinChangeDP(coins, amt)
println("目標金額を作るのに必要な最小硬貨枚数は $res")
// 空間最適化後の動的計画法
res = coinChangeDPComp(coins, amt)
println("目標金額を作るのに必要な最小硬貨枚数は $res")
}
@@ -0,0 +1,66 @@
/**
* File: coin_change_ii.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_dynamic_programming
/* コイン両替 II:動的計画法 */
fun coinChangeIIDP(coins: IntArray, amt: Int): Int {
val n = coins.size
// dp テーブルを初期化
val dp = Array(n + 1) { IntArray(amt + 1) }
// 先頭列を初期化する
for (i in 0..n) {
dp[i][0] = 1
}
// 状態遷移
for (i in 1..n) {
for (a in 1..amt) {
if (coins[i - 1] > a) {
// 目標金額を超えるなら硬貨 i は選ばない
dp[i][a] = dp[i - 1][a]
} else {
// コイン i を選ばない場合と選ぶ場合の和
dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]]
}
}
}
return dp[n][amt]
}
/* コイン両替 II:空間最適化した動的計画法 */
fun coinChangeIIDPComp(coins: IntArray, amt: Int): Int {
val n = coins.size
// dp テーブルを初期化
val dp = IntArray(amt + 1)
dp[0] = 1
// 状態遷移
for (i in 1..n) {
for (a in 1..amt) {
if (coins[i - 1] > a) {
// 目標金額を超えるなら硬貨 i は選ばない
dp[a] = dp[a]
} else {
// コイン i を選ばない場合と選ぶ場合の和
dp[a] = dp[a] + dp[a - coins[i - 1]]
}
}
}
return dp[amt]
}
/* Driver Code */
fun main() {
val coins = intArrayOf(1, 2, 5)
val amt = 5
// 動的計画法
var res = coinChangeIIDP(coins, amt)
println("目標金額を作る硬貨の組み合わせ数は $res")
// 空間最適化後の動的計画法
res = coinChangeIIDPComp(coins, amt)
println("目標金額を作る硬貨の組み合わせ数は $res")
}
@@ -0,0 +1,143 @@
/**
* File: edit_distance.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_dynamic_programming
import kotlin.math.min
/* 編集距離:総当たり探索 */
fun editDistanceDFS(
s: String,
t: String,
i: Int,
j: Int
): Int {
// s と t がともに空なら 0 を返す
if (i == 0 && j == 0) return 0
// s が空なら t の長さを返す
if (i == 0) return j
// t が空なら s の長さを返す
if (j == 0) return i
// 2 つの文字が等しければ、その 2 文字をそのままスキップする
if (s[i - 1] == t[j - 1]) return editDistanceDFS(s, t, i - 1, j - 1)
// 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1
val insert = editDistanceDFS(s, t, i, j - 1)
val delete = editDistanceDFS(s, t, i - 1, j)
val replace = editDistanceDFS(s, t, i - 1, j - 1)
// 最小編集回数を返す
return min(min(insert, delete), replace) + 1
}
/* 編集距離:メモ化探索 */
fun editDistanceDFSMem(
s: String,
t: String,
mem: Array<IntArray>,
i: Int,
j: Int
): Int {
// s と t がともに空なら 0 を返す
if (i == 0 && j == 0) return 0
// s が空なら t の長さを返す
if (i == 0) return j
// t が空なら s の長さを返す
if (j == 0) return i
// 記録済みなら、それをそのまま返す
if (mem[i][j] != -1) return mem[i][j]
// 2 つの文字が等しければ、その 2 文字をそのままスキップする
if (s[i - 1] == t[j - 1]) return editDistanceDFSMem(s, t, mem, i - 1, j - 1)
// 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1
val insert = editDistanceDFSMem(s, t, mem, i, j - 1)
val delete = editDistanceDFSMem(s, t, mem, i - 1, j)
val replace = editDistanceDFSMem(s, t, mem, i - 1, j - 1)
// 最小編集回数を記録して返す
mem[i][j] = min(min(insert, delete), replace) + 1
return mem[i][j]
}
/* 編集距離:動的計画法 */
fun editDistanceDP(s: String, t: String): Int {
val n = s.length
val m = t.length
val dp = Array(n + 1) { IntArray(m + 1) }
// 状態遷移:先頭行と先頭列
for (i in 1..n) {
dp[i][0] = i
}
for (j in 1..m) {
dp[0][j] = j
}
// 状態遷移: 残りの行と列
for (i in 1..n) {
for (j in 1..m) {
if (s[i - 1] == t[j - 1]) {
// 2 つの文字が等しければ、その 2 文字をそのままスキップする
dp[i][j] = dp[i - 1][j - 1]
} else {
// 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1
dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1
}
}
}
return dp[n][m]
}
/* 編集距離:空間最適化した動的計画法 */
fun editDistanceDPComp(s: String, t: String): Int {
val n = s.length
val m = t.length
val dp = IntArray(m + 1)
// 状態遷移:先頭行
for (j in 1..m) {
dp[j] = j
}
// 状態遷移:残りの行
for (i in 1..n) {
// 状態遷移:先頭列
var leftup = dp[0] // dp[i-1, j-1] を一時保存する
dp[0] = i
// 状態遷移:残りの列
for (j in 1..m) {
val temp = dp[j]
if (s[i - 1] == t[j - 1]) {
// 2 つの文字が等しければ、その 2 文字をそのままスキップする
dp[j] = leftup
} else {
// 最小編集回数 = 挿入・削除・置換の 3 操作における最小編集回数 + 1
dp[j] = min(min(dp[j - 1], dp[j]), leftup) + 1
}
leftup = temp // 次の反復の dp[i-1, j-1] に更新する
}
}
return dp[m]
}
/* Driver Code */
fun main() {
val s = "bag"
val t = "pack"
val n = s.length
val m = t.length
// 全探索
var res = editDistanceDFS(s, t, n, m)
println("$s$t に変更するには最小で $res 回の編集が必要")
// メモ化探索
val mem = Array(n + 1) { IntArray(m + 1) }
for (row in mem)
row.fill(-1)
res = editDistanceDFSMem(s, t, mem, n, m)
println("$s$t に変更するには最小で $res 回の編集が必要")
// 動的計画法
res = editDistanceDP(s, t)
println("$s$t に変更するには最小で $res 回の編集が必要")
// 空間最適化後の動的計画法
res = editDistanceDPComp(s, t)
println("$s$t に変更するには最小で $res 回の編集が必要")
}
@@ -0,0 +1,125 @@
/**
* File: knapsack.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_dynamic_programming
import kotlin.math.max
/* 0-1 ナップサック:総当たり探索 */
fun knapsackDFS(
wgt: IntArray,
_val: IntArray,
i: Int,
c: Int
): Int {
// すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す
if (i == 0 || c == 0) {
return 0
}
// ナップサック容量を超える場合は、入れない選択しかできない
if (wgt[i - 1] > c) {
return knapsackDFS(wgt, _val, i - 1, c)
}
// 品物 i を入れない場合と入れる場合の最大価値を計算する
val no = knapsackDFS(wgt, _val, i - 1, c)
val yes = knapsackDFS(wgt, _val, i - 1, c - wgt[i - 1]) + _val[i - 1]
// 2つの案のうち価値が大きいほうを返す
return max(no, yes)
}
/* 0-1 ナップサック:メモ化探索 */
fun knapsackDFSMem(
wgt: IntArray,
_val: IntArray,
mem: Array<IntArray>,
i: Int,
c: Int
): Int {
// すべての品物を選び終えたか、ナップサックに残り容量がなければ、価値 0 を返す
if (i == 0 || c == 0) {
return 0
}
// 既に記録があればそのまま返す
if (mem[i][c] != -1) {
return mem[i][c]
}
// ナップサック容量を超える場合は、入れない選択しかできない
if (wgt[i - 1] > c) {
return knapsackDFSMem(wgt, _val, mem, i - 1, c)
}
// 品物 i を入れない場合と入れる場合の最大価値を計算する
val no = knapsackDFSMem(wgt, _val, mem, i - 1, c)
val yes = knapsackDFSMem(wgt, _val, mem, i - 1, c - wgt[i - 1]) + _val[i - 1]
// 2 つの案のうち価値が大きい方を記録して返す
mem[i][c] = max(no, yes)
return mem[i][c]
}
/* 0-1 ナップサック:動的計画法 */
fun knapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int {
val n = wgt.size
// dp テーブルを初期化
val dp = Array(n + 1) { IntArray(cap + 1) }
// 状態遷移
for (i in 1..n) {
for (c in 1..cap) {
if (wgt[i - 1] > c) {
// ナップサック容量を超えるなら品物 i は選ばない
dp[i][c] = dp[i - 1][c]
} else {
// 品物 i を選ばない場合と選ぶ場合の大きい方
dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + _val[i - 1])
}
}
}
return dp[n][cap]
}
/* 0-1 ナップサック:空間最適化後の動的計画法 */
fun knapsackDPComp(wgt: IntArray, _val: IntArray, cap: Int): Int {
val n = wgt.size
// dp テーブルを初期化
val dp = IntArray(cap + 1)
// 状態遷移
for (i in 1..n) {
// 逆順に走査する
for (c in cap downTo 1) {
if (wgt[i - 1] <= c) {
// 品物 i を選ばない場合と選ぶ場合の大きい方
dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1])
}
}
}
return dp[cap]
}
/* Driver Code */
fun main() {
val wgt = intArrayOf(10, 20, 30, 40, 50)
val _val = intArrayOf(50, 120, 150, 210, 240)
val cap = 50
val n = wgt.size
// 全探索
var res = knapsackDFS(wgt, _val, n, cap)
println("ナップサック容量を超えない最大価値は $res")
// メモ化探索
val mem = Array(n + 1) { IntArray(cap + 1) }
for (row in mem) {
row.fill(-1)
}
res = knapsackDFSMem(wgt, _val, mem, n, cap)
println("ナップサック容量を超えない最大価値は $res")
// 動的計画法
res = knapsackDP(wgt, _val, cap)
println("ナップサック容量を超えない最大価値は $res")
// 空間最適化後の動的計画法
res = knapsackDPComp(wgt, _val, cap)
println("ナップサック容量を超えない最大価値は $res")
}
@@ -0,0 +1,51 @@
/**
* File: min_cost_climbing_stairs_dp.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_dynamic_programming
import kotlin.math.min
/* 階段登りの最小コスト:動的計画法 */
fun minCostClimbingStairsDP(cost: IntArray): Int {
val n = cost.size - 1
if (n == 1 || n == 2) return cost[n]
// 部分問題の解を保存するために dp テーブルを初期化
val dp = IntArray(n + 1)
// 初期状態:最小部分問題の解をあらかじめ設定
dp[1] = cost[1]
dp[2] = cost[2]
// 状態遷移:小さい部分問題から大きい部分問題へ順に解く
for (i in 3..n) {
dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i]
}
return dp[n]
}
/* 階段昇りの最小コスト:空間最適化後の動的計画法 */
fun minCostClimbingStairsDPComp(cost: IntArray): Int {
val n = cost.size - 1
if (n == 1 || n == 2) return cost[n]
var a = cost[1]
var b = cost[2]
for (i in 3..n) {
val tmp = b
b = min(a, tmp) + cost[i]
a = tmp
}
return b
}
/* Driver Code */
fun main() {
val cost = intArrayOf(0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1)
println("入力された階段コストのリストは ${cost.contentToString()}")
var res = minCostClimbingStairsDP(cost)
println("階段を上り切る最小コストは $res")
res = minCostClimbingStairsDPComp(cost)
println("階段を上り切る最小コストは $res")
}
@@ -0,0 +1,132 @@
/**
* File: min_path_sum.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_dynamic_programming
import kotlin.math.min
/* 最小経路和:全探索 */
fun minPathSumDFS(grid: Array<IntArray>, i: Int, j: Int): Int {
// 左上のセルなら探索を終了する
if (i == 0 && j == 0) {
return grid[0][0]
}
// 行または列のインデックスが範囲外なら、コスト +∞ を返す
if (i < 0 || j < 0) {
return Int.MAX_VALUE
}
// 左上から (i-1, j) および (i, j-1) までの最小経路コストを計算する
val up = minPathSumDFS(grid, i - 1, j)
val left = minPathSumDFS(grid, i, j - 1)
// 左上隅から (i, j) までの最小経路コストを返す
return min(left, up) + grid[i][j]
}
/* 最小経路和:メモ化探索 */
fun minPathSumDFSMem(
grid: Array<IntArray>,
mem: Array<IntArray>,
i: Int,
j: Int
): Int {
// 左上のセルなら探索を終了する
if (i == 0 && j == 0) {
return grid[0][0]
}
// 行または列のインデックスが範囲外なら、コスト +∞ を返す
if (i < 0 || j < 0) {
return Int.MAX_VALUE
}
// 既に記録があればそのまま返す
if (mem[i][j] != -1) {
return mem[i][j]
}
// 左と上のセルからの最小経路コスト
val up = minPathSumDFSMem(grid, mem, i - 1, j)
val left = minPathSumDFSMem(grid, mem, i, j - 1)
// 左上から (i, j) までの最小経路コストを記録して返す
mem[i][j] = min(left, up) + grid[i][j]
return mem[i][j]
}
/* 最小経路和:動的計画法 */
fun minPathSumDP(grid: Array<IntArray>): Int {
val n = grid.size
val m = grid[0].size
// dp テーブルを初期化
val dp = Array(n) { IntArray(m) }
dp[0][0] = grid[0][0]
// 状態遷移:先頭行
for (j in 1..<m) {
dp[0][j] = dp[0][j - 1] + grid[0][j]
}
// 状態遷移:先頭列
for (i in 1..<n) {
dp[i][0] = dp[i - 1][0] + grid[i][0]
}
// 状態遷移: 残りの行と列
for (i in 1..<n) {
for (j in 1..<m) {
dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j]
}
}
return dp[n - 1][m - 1]
}
/* 最小経路和:空間最適化後の動的計画法 */
fun minPathSumDPComp(grid: Array<IntArray>): Int {
val n = grid.size
val m = grid[0].size
// dp テーブルを初期化
val dp = IntArray(m)
// 状態遷移:先頭行
dp[0] = grid[0][0]
for (j in 1..<m) {
dp[j] = dp[j - 1] + grid[0][j]
}
// 状態遷移:残りの行
for (i in 1..<n) {
// 状態遷移:先頭列
dp[0] = dp[0] + grid[i][0]
// 状態遷移:残りの列
for (j in 1..<m) {
dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]
}
}
return dp[m - 1]
}
/* Driver Code */
fun main() {
val grid = arrayOf(
intArrayOf(1, 3, 1, 5),
intArrayOf(2, 2, 4, 2),
intArrayOf(5, 3, 2, 1),
intArrayOf(4, 3, 5, 2)
)
val n = grid.size
val m = grid[0].size
// 全探索
var res = minPathSumDFS(grid, n - 1, m - 1)
println("左上から右下までの最小経路和は $res")
// メモ化探索
val mem = Array(n) { IntArray(m) }
for (row in mem) {
row.fill(-1)
}
res = minPathSumDFSMem(grid, mem, n - 1, m - 1)
println("左上から右下までの最小経路和は $res")
// 動的計画法
res = minPathSumDP(grid)
println("左上から右下までの最小経路和は $res")
// 空間最適化後の動的計画法
res = minPathSumDPComp(grid)
println("左上から右下までの最小経路和は $res")
}
@@ -0,0 +1,68 @@
/**
* File: unbounded_knapsack.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_dynamic_programming
import kotlin.math.max
/* 完全ナップサック問題:動的計画法 */
fun unboundedKnapsackDP(wgt: IntArray, _val: IntArray, cap: Int): Int {
val n = wgt.size
// dp テーブルを初期化
val dp = Array(n + 1) { IntArray(cap + 1) }
// 状態遷移
for (i in 1..n) {
for (c in 1..cap) {
if (wgt[i - 1] > c) {
// ナップサック容量を超えるなら品物 i は選ばない
dp[i][c] = dp[i - 1][c]
} else {
// 品物 i を選ばない場合と選ぶ場合の大きい方
dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + _val[i - 1])
}
}
}
return dp[n][cap]
}
/* 完全ナップサック問題:空間最適化後の動的計画法 */
fun unboundedKnapsackDPComp(
wgt: IntArray,
_val: IntArray,
cap: Int
): Int {
val n = wgt.size
// dp テーブルを初期化
val dp = IntArray(cap + 1)
// 状態遷移
for (i in 1..n) {
for (c in 1..cap) {
if (wgt[i - 1] > c) {
// ナップサック容量を超えるなら品物 i は選ばない
dp[c] = dp[c]
} else {
// 品物 i を選ばない場合と選ぶ場合の大きい方
dp[c] = max(dp[c], dp[c - wgt[i - 1]] + _val[i - 1])
}
}
}
return dp[cap]
}
/* Driver Code */
fun main() {
val wgt = intArrayOf(1, 2, 3)
val _val = intArrayOf(5, 11, 15)
val cap = 4
// 動的計画法
var res = unboundedKnapsackDP(wgt, _val, cap)
println("ナップサック容量を超えない最大価値は $res")
// 空間最適化後の動的計画法
res = unboundedKnapsackDPComp(wgt, _val, cap)
println("ナップサック容量を超えない最大価値は $res")
}
@@ -0,0 +1,121 @@
/**
* File: graph_adjacency_list.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_graph
import utils.Vertex
/* 隣接リストに基づく無向グラフクラス */
class GraphAdjList(edges: Array<Array<Vertex?>>) {
// 隣接リスト。key は頂点、value はその頂点に隣接する全頂点
val adjList = HashMap<Vertex, MutableList<Vertex>>()
/* コンストラクタ */
init {
// すべての頂点と辺を追加
for (edge in edges) {
addVertex(edge[0]!!)
addVertex(edge[1]!!)
addEdge(edge[0]!!, edge[1]!!)
}
}
/* 頂点数を取得 */
fun size(): Int {
return adjList.size
}
/* 辺を追加 */
fun addEdge(vet1: Vertex, vet2: Vertex) {
if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)
throw IllegalArgumentException()
// 辺 vet1 - vet2 を追加
adjList[vet1]?.add(vet2)
adjList[vet2]?.add(vet1)
}
/* 辺を削除 */
fun removeEdge(vet1: Vertex, vet2: Vertex) {
if (!adjList.containsKey(vet1) || !adjList.containsKey(vet2) || vet1 == vet2)
throw IllegalArgumentException()
// 辺 vet1 - vet2 を削除
adjList[vet1]?.remove(vet2)
adjList[vet2]?.remove(vet1)
}
/* 頂点を追加 */
fun addVertex(vet: Vertex) {
if (adjList.containsKey(vet))
return
// 隣接リストに新しいリストを追加
adjList[vet] = mutableListOf()
}
/* 頂点を削除 */
fun removeVertex(vet: Vertex) {
if (!adjList.containsKey(vet))
throw IllegalArgumentException()
// 隣接リストから頂点 vet に対応するリストを削除
adjList.remove(vet)
// 他の頂点のリストを走査し、vet を含むすべての辺を削除
for (list in adjList.values) {
list.remove(vet)
}
}
/* 隣接リストを出力 */
fun print() {
println("隣接リスト =")
for (pair in adjList.entries) {
val tmp = mutableListOf<Int>()
for (vertex in pair.value) {
tmp.add(vertex._val)
}
println("${pair.key._val}: $tmp,")
}
}
}
/* Driver Code */
fun main() {
/* 無向グラフを初期化 */
val v = Vertex.valsToVets(intArrayOf(1, 3, 2, 5, 4))
val edges = arrayOf(
arrayOf(v[0], v[1]),
arrayOf(v[0], v[3]),
arrayOf(v[1], v[2]),
arrayOf(v[2], v[3]),
arrayOf(v[2], v[4]),
arrayOf(v[3], v[4])
)
val graph = GraphAdjList(edges)
println("\n初期化後のグラフは")
graph.print()
/* 辺を追加 */
// 頂点 1, 2 は v[0], v[2]
graph.addEdge(v[0]!!, v[2]!!)
println("\n辺 1-2 を追加した後のグラフは")
graph.print()
/* 辺を削除 */
// 頂点 1, 3 は v[0], v[1]
graph.removeEdge(v[0]!!, v[1]!!)
println("\n辺 1-3 を削除した後のグラフは")
graph.print()
/* 頂点を追加 */
val v5 = Vertex(6)
graph.addVertex(v5)
println("\n頂点 6 を追加した後のグラフは")
graph.print()
/* 頂点を削除 */
// 頂点 3 は v[1]
graph.removeVertex(v[1]!!)
println("\n頂点 3 を削除した後のグラフは")
graph.print()
}
@@ -0,0 +1,134 @@
/**
* File: graph_adjacency_matrix.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_graph
import utils.printMatrix
/* 隣接行列に基づく無向グラフクラス */
class GraphAdjMat(vertices: IntArray, edges: Array<IntArray>) {
val vertices = mutableListOf<Int>() // 頂点リスト。要素は「頂点値」、インデックスは「頂点インデックス」を表す
val adjMat = mutableListOf<MutableList<Int>>() // 隣接行列。行・列のインデックスは「頂点インデックス」に対応
/* コンストラクタ */
init {
// 頂点を追加
for (vertex in vertices) {
addVertex(vertex)
}
// 辺を追加
// 注意:edges の各要素は頂点インデックスを表し、vertices の要素インデックスに対応する
for (edge in edges) {
addEdge(edge[0], edge[1])
}
}
/* 頂点数を取得 */
fun size(): Int {
return vertices.size
}
/* 頂点を追加 */
fun addVertex(_val: Int) {
val n = size()
// 頂点リストに新しい頂点の値を追加
vertices.add(_val)
// 隣接行列に 1 行追加
val newRow = mutableListOf<Int>()
for (j in 0..<n) {
newRow.add(0)
}
adjMat.add(newRow)
// 隣接行列に 1 列追加
for (row in adjMat) {
row.add(0)
}
}
/* 頂点を削除 */
fun removeVertex(index: Int) {
if (index >= size())
throw IndexOutOfBoundsException()
// 頂点リストから index の頂点を削除する
vertices.removeAt(index)
// 隣接行列で index 行を削除する
adjMat.removeAt(index)
// 隣接行列で index 列を削除する
for (row in adjMat) {
row.removeAt(index)
}
}
/* 辺を追加 */
// 引数 i, j は vertices の要素インデックスに対応する
fun addEdge(i: Int, j: Int) {
// インデックスの範囲外と等値の処理
if (i < 0 || j < 0 || i >= size() || j >= size() || i == j)
throw IndexOutOfBoundsException()
// 無向グラフでは、隣接行列は主対角線に関して対称、すなわち (i, j) == (j, i) を満たす
adjMat[i][j] = 1
adjMat[j][i] = 1
}
/* 辺を削除 */
// 引数 i, j は vertices の要素インデックスに対応する
fun removeEdge(i: Int, j: Int) {
// インデックスの範囲外と等値の処理
if (i < 0 || j < 0 || i >= size() || j >= size() || i == j)
throw IndexOutOfBoundsException()
adjMat[i][j] = 0
adjMat[j][i] = 0
}
/* 隣接行列を出力 */
fun print() {
print("頂点リスト = ")
println(vertices)
println("隣接行列 =")
printMatrix(adjMat)
}
}
/* Driver Code */
fun main() {
/* 無向グラフを初期化 */
// edges の要素は頂点インデックス、すなわち vertices の要素インデックスに対応する点に注意
val vertices = intArrayOf(1, 3, 2, 5, 4)
val edges = arrayOf(
intArrayOf(0, 1),
intArrayOf(0, 3),
intArrayOf(1, 2),
intArrayOf(2, 3),
intArrayOf(2, 4),
intArrayOf(3, 4)
)
val graph = GraphAdjMat(vertices, edges)
println("\n初期化後のグラフは")
graph.print()
/* 辺を追加 */
// 頂点 1, 2 のインデックスはそれぞれ 0, 2
graph.addEdge(0, 2)
println("\n辺 1-2 を追加した後のグラフは")
graph.print()
/* 辺を削除 */
// 頂点 1, 3 のインデックスはそれぞれ 0, 1
graph.removeEdge(0, 1)
println("\n辺 1-3 を削除した後のグラフは")
graph.print()
/* 頂点を追加 */
graph.addVertex(6)
println("\n頂点 6 を追加した後のグラフは")
graph.print()
/* 頂点を削除 */
// 頂点 3 のインデックスは 1
graph.removeVertex(1)
println("\n頂点 3 を削除した後のグラフは")
graph.print()
}
@@ -0,0 +1,65 @@
/**
* File: graph_bfs.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_graph
import utils.Vertex
import java.util.*
/* 幅優先探索 */
// グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする
fun graphBFS(graph: GraphAdjList, startVet: Vertex): MutableList<Vertex?> {
// 頂点の走査順序
val res = mutableListOf<Vertex?>()
// 訪問済み頂点を記録するためのハッシュ集合
val visited = HashSet<Vertex>()
visited.add(startVet)
// BFS の実装にキューを用いる
val que = LinkedList<Vertex>()
que.offer(startVet)
// 頂点 vet を起点に、すべての頂点を訪問し終えるまで繰り返す
while (!que.isEmpty()) {
val vet = que.poll() // 先頭の頂点をデキュー
res.add(vet) // 訪問した頂点を記録
// この頂点のすべての隣接頂点を走査
for (adjVet in graph.adjList[vet]!!) {
if (visited.contains(adjVet))
continue // 訪問済みの頂点をスキップ
que.offer(adjVet) // 未訪問の頂点のみをキューに追加
visited.add(adjVet) // この頂点を訪問済みにする
}
}
// 頂点の走査順を返す
return res
}
/* Driver Code */
fun main() {
/* 無向グラフを初期化 */
val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9))
val edges = arrayOf(
arrayOf(v[0], v[1]),
arrayOf(v[0], v[3]),
arrayOf(v[1], v[2]),
arrayOf(v[1], v[4]),
arrayOf(v[2], v[5]),
arrayOf(v[3], v[4]),
arrayOf(v[3], v[6]),
arrayOf(v[4], v[5]),
arrayOf(v[4], v[7]),
arrayOf(v[5], v[8]),
arrayOf(v[6], v[7]),
arrayOf(v[7], v[8])
)
val graph = GraphAdjList(edges)
println("\n初期化後のグラフは")
graph.print()
/* 幅優先探索 */
val res = graphBFS(graph, v[0]!!)
println("\n幅優先探索(BFS)の頂点順序は")
println(Vertex.vetsToVals(res))
}
@@ -0,0 +1,60 @@
/**
* File: graph_dfs.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_graph
import utils.Vertex
/* 深さ優先走査の補助関数 */
fun dfs(
graph: GraphAdjList,
visited: MutableSet<Vertex?>,
res: MutableList<Vertex?>,
vet: Vertex?
) {
res.add(vet) // 訪問した頂点を記録
visited.add(vet) // この頂点を訪問済みにする
// この頂点のすべての隣接頂点を走査
for (adjVet in graph.adjList[vet]!!) {
if (visited.contains(adjVet))
continue // 訪問済みの頂点をスキップ
// 隣接頂点を再帰的に訪問
dfs(graph, visited, res, adjVet)
}
}
/* 深さ優先探索 */
// グラフを隣接リストで表し、指定した頂点の隣接頂点をすべて取得できるようにする
fun graphDFS(graph: GraphAdjList, startVet: Vertex?): MutableList<Vertex?> {
// 頂点の走査順序
val res = mutableListOf<Vertex?>()
// 訪問済み頂点を記録するためのハッシュ集合
val visited = HashSet<Vertex?>()
dfs(graph, visited, res, startVet)
return res
}
/* Driver Code */
fun main() {
/* 無向グラフを初期化 */
val v = Vertex.valsToVets(intArrayOf(0, 1, 2, 3, 4, 5, 6))
val edges = arrayOf(
arrayOf(v[0], v[1]),
arrayOf(v[0], v[3]),
arrayOf(v[1], v[2]),
arrayOf(v[2], v[5]),
arrayOf(v[4], v[5]),
arrayOf(v[5], v[6])
)
val graph = GraphAdjList(edges)
println("\n初期化後のグラフは")
graph.print()
/* 深さ優先探索 */
val res = graphDFS(graph, v[0])
println("\n深さ優先探索(DFS)の頂点順序は")
println(Vertex.vetsToVals(res))
}
@@ -0,0 +1,53 @@
/**
* File: coin_change_greedy.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_greedy
/* コイン交換:貪欲法 */
fun coinChangeGreedy(coins: IntArray, amt: Int): Int {
// coins リストはソート済みと仮定する
var am = amt
var i = coins.size - 1
var count = 0
// 残額がなくなるまで貪欲選択を繰り返す
while (am > 0) {
// 残額以下で最も近い硬貨を見つける
while (i > 0 && coins[i] > am) {
i--
}
// coins[i] を選択する
am -= coins[i]
count++
}
// 実行可能な解が見つからなければ -1 を返す
return if (am == 0) count else -1
}
/* Driver Code */
fun main() {
// 貪欲法:大域最適解を保証できる
var coins = intArrayOf(1, 5, 10, 20, 50, 100)
var amt = 186
var res = coinChangeGreedy(coins, amt)
println("\ncoins = ${coins.contentToString()}, amt = $amt")
println("$amt を作るのに必要な最小硬貨枚数は $res")
// 貪欲法:大域最適解を保証できない
coins = intArrayOf(1, 20, 50)
amt = 60
res = coinChangeGreedy(coins, amt)
println("\ncoins = ${coins.contentToString()}, amt = $amt")
println("$amt を作るのに必要な最小硬貨枚数は $res")
println("実際に必要な最小枚数は 3、つまり 20 + 20 + 20")
// 貪欲法:大域最適解を保証できない
coins = intArrayOf(1, 49, 50)
amt = 98
res = coinChangeGreedy(coins, amt)
println("\ncoins = ${coins.contentToString()}, amt = $amt")
println("$amt を作るのに必要な最小硬貨枚数は $res")
println("実際に必要な最小枚数は 2、つまり 49 + 49")
}
@@ -0,0 +1,51 @@
/**
* File: fractional_knapsack.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_greedy
/* 品物 */
class Item(
val w: Int, // 品物
val v: Int // 品物の価値
)
/* 分数ナップサック:貪欲法 */
fun fractionalKnapsack(wgt: IntArray, _val: IntArray, c: Int): Double {
// 重さと価値の 2 属性を持つ品物リストを作成
var cap = c
val items = arrayOfNulls<Item>(wgt.size)
for (i in wgt.indices) {
items[i] = Item(wgt[i], _val[i])
}
// 単位価値 item.v / item.w の高い順にソートする
items.sortBy { item: Item? -> -(item!!.v.toDouble() / item.w) }
// 貪欲選択を繰り返す
var res = 0.0
for (item in items) {
if (item!!.w <= cap) {
// 残り容量が十分なら、現在の品物を丸ごとナップサックに入れる
res += item.v
cap -= item.w
} else {
// 残り容量が足りない場合は、現在の品物の一部だけをナップサックに入れる
res += item.v.toDouble() / item.w * cap
// 残り容量がないため、ループを抜ける
break
}
}
return res
}
/* Driver Code */
fun main() {
val wgt = intArrayOf(10, 20, 30, 40, 50)
val _val = intArrayOf(50, 120, 150, 210, 240)
val cap = 50
// 貪欲法
val res = fractionalKnapsack(wgt, _val, cap)
println("ナップサック容量を超えない最大価値は $res")
}
@@ -0,0 +1,41 @@
/**
* File: max_capacity.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_greedy
import kotlin.math.max
import kotlin.math.min
/* 最大容量:貪欲法 */
fun maxCapacity(ht: IntArray): Int {
// i, j を初期化し、それぞれ配列の両端に置く
var i = 0
var j = ht.size - 1
// 初期の最大容量は 0
var res = 0
// 2 枚の板が出会うまで貪欲選択を繰り返す
while (i < j) {
// 最大容量を更新する
val cap = min(ht[i], ht[j]) * (j - i)
res = max(res, cap)
// 短い方を内側へ動かす
if (ht[i] < ht[j]) {
i++
} else {
j--
}
}
return res
}
/* Driver Code */
fun main() {
val ht = intArrayOf(3, 8, 5, 2, 7, 7, 3, 4)
// 貪欲法
val res = maxCapacity(ht)
println("最大容量は $res")
}
@@ -0,0 +1,39 @@
/**
* File: max_product_cutting.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_greedy
import kotlin.math.pow
/* 最大切断積:貪欲法 */
fun maxProductCutting(n: Int): Int {
// n <= 3 のときは、必ず 1 を切り出す
if (n <= 3) {
return 1 * (n - 1)
}
// 貪欲に 3 を切り出し、a を 3 の個数、b を余りとする
val a = n / 3
val b = n % 3
if (b == 1) {
// 余りが 1 のときは、1 * 3 を 2 * 2 に変える
return 3.0.pow((a - 1)).toInt() * 2 * 2
}
if (b == 2) {
// 余りが 2 のときは、そのままにする
return 3.0.pow(a).toInt() * 2 * 2
}
// 余りが 0 のときは、そのままにする
return 3.0.pow(a).toInt()
}
/* Driver Code */
fun main() {
val n = 58
// 貪欲法
val res = maxProductCutting(n)
println("最大分割積は $res")
}
@@ -0,0 +1,126 @@
/**
* File: array_hash_map.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_hashing
/* キーと値の組 */
class Pair(
var key: Int,
var _val: String
)
/* 配列ベースのハッシュテーブル */
class ArrayHashMap {
// 100 個のバケットを含む配列を初期化
private val buckets = arrayOfNulls<Pair>(100)
/* ハッシュ関数 */
fun hashFunc(key: Int): Int {
val index = key % 100
return index
}
/* 検索操作 */
fun get(key: Int): String? {
val index = hashFunc(key)
val pair = buckets[index] ?: return null
return pair._val
}
/* 追加操作 */
fun put(key: Int, _val: String) {
val pair = Pair(key, _val)
val index = hashFunc(key)
buckets[index] = pair
}
/* 削除操作 */
fun remove(key: Int) {
val index = hashFunc(key)
// null に設定し、削除を表す
buckets[index] = null
}
/* すべてのキーと値のペアを取得 */
fun pairSet(): MutableList<Pair> {
val pairSet = mutableListOf<Pair>()
for (pair in buckets) {
if (pair != null)
pairSet.add(pair)
}
return pairSet
}
/* すべてのキーを取得 */
fun keySet(): MutableList<Int> {
val keySet = mutableListOf<Int>()
for (pair in buckets) {
if (pair != null)
keySet.add(pair.key)
}
return keySet
}
/* すべての値を取得 */
fun valueSet(): MutableList<String> {
val valueSet = mutableListOf<String>()
for (pair in buckets) {
if (pair != null)
valueSet.add(pair._val)
}
return valueSet
}
/* ハッシュテーブルを出力 */
fun print() {
for (kv in pairSet()) {
val key = kv.key
val _val = kv._val
println("$key -> $_val")
}
}
}
/* Driver Code */
fun main() {
/* ハッシュテーブルを初期化 */
val map = ArrayHashMap()
/* 追加操作 */
// ハッシュテーブルにキーと値のペア (key, value) を追加
map.put(12836, "シャオハー")
map.put(15937, "シャオルオ")
map.put(16750, "シャオスワン")
map.put(13276, "シャオファー")
map.put(10583, "シャオヤ")
println("\n追加完了後、ハッシュテーブルは\nKey -> Value")
map.print()
/* 検索操作 */
// キー key をハッシュテーブルに渡し、値 value を取得
val name = map.get(15937)
println("\n学籍番号 15937 を入力すると、名前 $name が見つかりました")
/* 削除操作 */
// ハッシュテーブルからキーと値のペア (key, value) を削除
map.remove(10583)
println("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value")
map.print()
/* ハッシュテーブルを走査 */
println("\nキーと値のペアを走査 Key -> Value")
for (kv in map.pairSet()) {
println("${kv.key} -> ${kv._val}")
}
println("\nキー Key を個別に走査")
for (key in map.keySet()) {
println(key)
}
println("\n値 Value を個別に走査")
for (_val in map.valueSet()) {
println(_val)
}
}
@@ -0,0 +1,36 @@
/**
* File: built_in_hash.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_hashing
import utils.ListNode
/* Driver Code */
fun main() {
val num = 3
val hashNum = num.hashCode()
println("整数 $num のハッシュ値は $hashNum")
val bol = true
val hashBol = bol.hashCode()
println("真偽値 $bol のハッシュ値は $hashBol")
val dec = 3.14159
val hashDec = dec.hashCode()
println("小数 $dec のハッシュ値は $hashDec")
val str = "Hello アルゴリズム"
val hashStr = str.hashCode()
println("文字列 $str のハッシュ値は $hashStr")
val arr = arrayOf<Any>(12836, "シャオハー")
val hashTup = arr.contentHashCode()
println("配列 ${arr.contentToString()} のハッシュ値は $hashTup")
val obj = ListNode(0)
val hashObj = obj.hashCode()
println("ノードオブジェクト $obj のハッシュ値は $hashObj")
}
@@ -0,0 +1,50 @@
/**
* File: hash_map.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_hashing
import utils.printHashMap
/* Driver Code */
fun main() {
/* ハッシュテーブルを初期化 */
val map = HashMap<Int, String>()
/* 追加操作 */
// ハッシュテーブルにキーと値のペア (key, value) を追加
map[12836] = "シャオハー"
map[15937] = "シャオルオ"
map[16750] = "シャオスワン"
map[13276] = "シャオファー"
map[10583] = "シャオヤ"
println("\n追加完了後、ハッシュテーブルは\nKey -> Value")
printHashMap(map)
/* 検索操作 */
// キー key をハッシュテーブルに渡し、値 value を取得
val name = map[15937]
println("\n学籍番号 15937 を入力すると、名前 $name が見つかりました")
/* 削除操作 */
// ハッシュテーブルからキーと値のペア (key, value) を削除
map.remove(10583)
println("\n10583 を削除した後、ハッシュテーブルは\nKey -> Value")
printHashMap(map)
/* ハッシュテーブルを走査 */
println("\nキーと値のペアを走査 Key->Value")
for ((key, value) in map) {
println("$key -> $value")
}
println("\nキー Key を個別に走査")
for (key in map.keys) {
println(key)
}
println("\n値 Value を個別に走査")
for (_val in map.values) {
println(_val)
}
}
@@ -0,0 +1,145 @@
/**
* File: hash_map_chaining.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_hashing
/* チェイン法ハッシュテーブル */
class HashMapChaining {
var size: Int // キーと値のペア数
var capacity: Int // ハッシュテーブル容量
val loadThres: Double // リサイズを発動する負荷率のしきい値
val extendRatio: Int // 拡張倍率
var buckets: MutableList<MutableList<Pair>> // バケット配列
/* コンストラクタ */
init {
size = 0
capacity = 4
loadThres = 2.0 / 3.0
extendRatio = 2
buckets = mutableListOf()
for (i in 0..<capacity) {
buckets.add(mutableListOf())
}
}
/* ハッシュ関数 */
fun hashFunc(key: Int): Int {
return key % capacity
}
/* 負荷率 */
fun loadFactor(): Double {
return (size / capacity).toDouble()
}
/* 検索操作 */
fun get(key: Int): String? {
val index = hashFunc(key)
val bucket = buckets[index]
// バケットを走査し、key が見つかれば対応する val を返す
for (pair in bucket) {
if (pair.key == key) return pair._val
}
// key が見つからない場合は null を返す
return null
}
/* 追加操作 */
fun put(key: Int, _val: String) {
// 負荷率がしきい値を超えたら、リサイズを実行
if (loadFactor() > loadThres) {
extend()
}
val index = hashFunc(key)
val bucket = buckets[index]
// バケットを走査し、指定した key が見つかれば対応する val を更新して返す
for (pair in bucket) {
if (pair.key == key) {
pair._val = _val
return
}
}
// その key が存在しなければ、キーと値のペアを末尾に追加
val pair = Pair(key, _val)
bucket.add(pair)
size++
}
/* 削除操作 */
fun remove(key: Int) {
val index = hashFunc(key)
val bucket = buckets[index]
// バケットを走査してキーと値のペアを削除
for (pair in bucket) {
if (pair.key == key) {
bucket.remove(pair)
size--
break
}
}
}
/* ハッシュテーブルを拡張 */
fun extend() {
// 元のハッシュテーブルを一時保存
val bucketsTmp = buckets
// リサイズ後の新しいハッシュテーブルを初期化
capacity *= extendRatio
// mutablelist には固定サイズがない
buckets = mutableListOf()
for (i in 0..<capacity) {
buckets.add(mutableListOf())
}
size = 0
// キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す
for (bucket in bucketsTmp) {
for (pair in bucket) {
put(pair.key, pair._val)
}
}
}
/* ハッシュテーブルを出力 */
fun print() {
for (bucket in buckets) {
val res = mutableListOf<String>()
for (pair in bucket) {
val k = pair.key
val v = pair._val
res.add("$k -> $v")
}
println(res)
}
}
}
/* Driver Code */
fun main() {
/* ハッシュテーブルを初期化 */
val map = HashMapChaining()
/* 追加操作 */
// ハッシュテーブルにキーと値のペア (key, value) を追加
map.put(12836, "シャオハー")
map.put(15937, "シャオルオ")
map.put(16750, "シャオスワン")
map.put(13276, "シャオファー")
map.put(10583, "シャオヤ")
println("\n追加完了後、ハッシュテーブルは\nKey -> Value")
map.print()
/* 検索操作 */
// キー key をハッシュテーブルに渡し、値 value を取得
val name = map.get(13276)
println("\n学籍番号 13276 を入力すると、名前 $name が見つかりました")
/* 削除操作 */
// ハッシュテーブルからキーと値のペア (key, value) を削除
map.remove(12836)
println("\n12836 を削除した後、ハッシュテーブルは\nKey -> Value")
map.print()
}
@@ -0,0 +1,161 @@
/**
* File: hash_map_open_addressing.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_hashing
/* オープンアドレス法ハッシュテーブル */
class HashMapOpenAddressing {
private var size: Int // キーと値のペア数
private var capacity: Int // ハッシュテーブル容量
private val loadThres: Double // リサイズを発動する負荷率のしきい値
private val extendRatio: Int // 拡張倍率
private var buckets: Array<Pair?> // バケット配列
private val TOMBSTONE: Pair // 削除済みマーク
/* コンストラクタ */
init {
size = 0
capacity = 4
loadThres = 2.0 / 3.0
extendRatio = 2
buckets = arrayOfNulls(capacity)
TOMBSTONE = Pair(-1, "-1")
}
/* ハッシュ関数 */
fun hashFunc(key: Int): Int {
return key % capacity
}
/* 負荷率 */
fun loadFactor(): Double {
return (size / capacity).toDouble()
}
/* key に対応するバケットインデックスを探す */
fun findBucket(key: Int): Int {
var index = hashFunc(key)
var firstTombstone = -1
// 線形プロービングを行い、空バケットに達したら終了
while (buckets[index] != null) {
// key が見つかったら、対応するバケットのインデックスを返す
if (buckets[index]?.key == key) {
// 以前に削除マークが見つかっていれば、そのインデックスへキーと値のペアを移動
if (firstTombstone != -1) {
buckets[firstTombstone] = buckets[index]
buckets[index] = TOMBSTONE
return firstTombstone // 移動後のバケットインデックスを返す
}
return index // バケットのインデックスを返す
}
// 最初に見つかった削除マークを記録
if (firstTombstone == -1 && buckets[index] == TOMBSTONE) {
firstTombstone = index
}
// バケットのインデックスを計算し、末尾を越えたら先頭に戻る
index = (index + 1) % capacity
}
// key が存在しない場合は追加位置のインデックスを返す
return if (firstTombstone == -1) index else firstTombstone
}
/* 検索操作 */
fun get(key: Int): String? {
// key に対応するバケットインデックスを探す
val index = findBucket(key)
// キーと値の組が見つかったら、対応する val を返す
if (buckets[index] != null && buckets[index] != TOMBSTONE) {
return buckets[index]?._val
}
// キーと値の組が存在しなければ null を返す
return null
}
/* 追加操作 */
fun put(key: Int, _val: String) {
// 負荷率がしきい値を超えたら、リサイズを実行
if (loadFactor() > loadThres) {
extend()
}
// key に対応するバケットインデックスを探す
val index = findBucket(key)
// キーと値の組が見つかったら、val を上書きして返す
if (buckets[index] != null && buckets[index] != TOMBSTONE) {
buckets[index]!!._val = _val
return
}
// キーと値の組が存在しない場合は、その組を追加する
buckets[index] = Pair(key, _val)
size++
}
/* 削除操作 */
fun remove(key: Int) {
// key に対応するバケットインデックスを探す
val index = findBucket(key)
// キーと値の組が見つかったら、削除マーカーで上書きする
if (buckets[index] != null && buckets[index] != TOMBSTONE) {
buckets[index] = TOMBSTONE
size--
}
}
/* ハッシュテーブルを拡張 */
fun extend() {
// 元のハッシュテーブルを一時保存
val bucketsTmp = buckets
// リサイズ後の新しいハッシュテーブルを初期化
capacity *= extendRatio
buckets = arrayOfNulls(capacity)
size = 0
// キーと値のペアを元のハッシュテーブルから新しいハッシュテーブルへ移す
for (pair in bucketsTmp) {
if (pair != null && pair != TOMBSTONE) {
put(pair.key, pair._val)
}
}
}
/* ハッシュテーブルを出力 */
fun print() {
for (pair in buckets) {
if (pair == null) {
println("null")
} else if (pair == TOMBSTONE) {
println("TOMESTOME")
} else {
println("${pair.key} -> ${pair._val}")
}
}
}
}
/* Driver Code */
fun main() {
// ハッシュテーブルを初期化
val hashmap = HashMapOpenAddressing()
// 追加操作
// ハッシュテーブルにキーと値の組 (key, val) を追加する
hashmap.put(12836, "シャオハー")
hashmap.put(15937, "シャオルオ")
hashmap.put(16750, "シャオスワン")
hashmap.put(13276, "シャオファー")
hashmap.put(10583, "シャオヤー")
println("\n追加完了後、ハッシュテーブルは\nKey -> Value")
hashmap.print()
// 検索操作
// ハッシュテーブルにキー key を入力し、値 val を得る
val name = hashmap.get(13276)
println("\n学籍番号 13276 を入力すると、名前 $name が見つかりました")
// 削除操作
// ハッシュテーブルからキーと値の組 (key, val) を削除する
hashmap.remove(16750)
println("\n16750 を削除した後、ハッシュテーブルは\nKey -> Value")
hashmap.print()
}
@@ -0,0 +1,64 @@
/**
* File: simple_hash.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_hashing
/* 加算ハッシュ */
fun addHash(key: String): Int {
var hash = 0L
val MODULUS = 1000000007
for (c in key.toCharArray()) {
hash = (hash + c.code) % MODULUS
}
return hash.toInt()
}
/* 乗算ハッシュ */
fun mulHash(key: String): Int {
var hash = 0L
val MODULUS = 1000000007
for (c in key.toCharArray()) {
hash = (31 * hash + c.code) % MODULUS
}
return hash.toInt()
}
/* XOR ハッシュ */
fun xorHash(key: String): Int {
var hash = 0
val MODULUS = 1000000007
for (c in key.toCharArray()) {
hash = hash xor c.code
}
return hash and MODULUS
}
/* 回転ハッシュ */
fun rotHash(key: String): Int {
var hash = 0L
val MODULUS = 1000000007
for (c in key.toCharArray()) {
hash = ((hash shl 4) xor (hash shr 28) xor c.code.toLong()) % MODULUS
}
return hash.toInt()
}
/* Driver Code */
fun main() {
val key = "Hello アルゴリズム"
var hash = addHash(key)
println("加算ハッシュ値は $hash")
hash = mulHash(key)
println("乗算ハッシュ値は $hash")
hash = xorHash(key)
println("XORハッシュ値は $hash")
hash = rotHash(key)
println("回転ハッシュ値は $hash")
}
+66
View File
@@ -0,0 +1,66 @@
/**
* File: heap.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_heap
import utils.printHeap
import java.util.*
fun testPush(heap: Queue<Int>, _val: Int) {
heap.offer(_val) // 要素をヒープに追加
print("\n要素 $_val をヒープに追加した後\n")
printHeap(heap)
}
fun testPop(heap: Queue<Int>) {
val _val = heap.poll() // ヒープ頂点の要素を取り出す
print("\nヒープ先頭要素 $_val を取り出した後\n")
printHeap(heap)
}
/* Driver Code */
fun main() {
/* ヒープを初期化 */
// 最小ヒープを初期化
var minHeap = PriorityQueue<Int>()
// 最大ヒープを初期化する(lambda 式で Comparator を変更すればよい)
val maxHeap = PriorityQueue { a: Int, b: Int -> b - a }
println("\n以下のテストケースは最大ヒープです")
/* 要素をヒープに追加 */
testPush(maxHeap, 1)
testPush(maxHeap, 3)
testPush(maxHeap, 2)
testPush(maxHeap, 5)
testPush(maxHeap, 4)
/* ヒープ頂点の要素を取得 */
val peek = maxHeap.peek()
print("\nヒープ先頭要素は $peek\n")
/* ヒープ頂点の要素を取り出す */
testPop(maxHeap)
testPop(maxHeap)
testPop(maxHeap)
testPop(maxHeap)
testPop(maxHeap)
/* ヒープのサイズを取得 */
val size = maxHeap.size
print("\nヒープ要素数は $size\n")
/* ヒープが空かどうかを判定 */
val isEmpty = maxHeap.isEmpty()
print("\nヒープが空かどうか $isEmpty\n")
/* リストを入力してヒープを構築 */
// 時間計算量は O(n) であり、O(nlogn) ではない
minHeap = PriorityQueue(mutableListOf<Int?>(1, 3, 2, 5, 4))
println("\n入力リストから最小ヒープを構築した後")
printHeap(minHeap)
}
+160
View File
@@ -0,0 +1,160 @@
/**
* File: my_heap.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_heap
import utils.printHeap
import java.util.*
/* 最大ヒープ */
class MaxHeap(nums: MutableList<Int>?) {
// 配列ではなくリストを使うことで、拡張を考慮する必要がない
private val maxHeap = mutableListOf<Int>()
/* コンストラクタ。入力リストに基づいてヒープを構築する */
init {
// リスト要素をそのままヒープに追加
maxHeap.addAll(nums!!)
// 葉ノード以外のすべてのノードをヒープ化
for (i in parent(size() - 1) downTo 0) {
siftDown(i)
}
}
/* 左子ノードのインデックスを取得 */
private fun left(i: Int): Int {
return 2 * i + 1
}
/* 右子ノードのインデックスを取得 */
private fun right(i: Int): Int {
return 2 * i + 2
}
/* 親ノードのインデックスを取得 */
private fun parent(i: Int): Int {
return (i - 1) / 2 // 切り捨て除算
}
/* 要素を交換 */
private fun swap(i: Int, j: Int) {
val temp = maxHeap[i]
maxHeap[i] = maxHeap[j]
maxHeap[j] = temp
}
/* ヒープのサイズを取得 */
fun size(): Int {
return maxHeap.size
}
/* ヒープが空かどうかを判定 */
fun isEmpty(): Boolean {
/* ヒープが空かどうかを判定 */
return size() == 0
}
/* ヒープ先頭要素にアクセス */
fun peek(): Int {
return maxHeap[0]
}
/* 要素をヒープに追加 */
fun push(_val: Int) {
// ノードを追加
maxHeap.add(_val)
// 下から上へヒープ化
siftUp(size() - 1)
}
/* ノード i から始めて、下から上へヒープ化 */
private fun siftUp(it: Int) {
// Kotlin の関数引数は不変のため、一時変数を作成する
var i = it
while (true) {
// ノード i の親ノードを取得
val p = parent(i)
// 「根ノードを越えた」または「ノードの修復が不要」になったらヒープ化を終了
if (p < 0 || maxHeap[i] <= maxHeap[p]) break
// 2 つのノードを交換
swap(i, p)
// ループで下から上へヒープ化
i = p
}
}
/* 要素をヒープから取り出す */
fun pop(): Int {
// 空判定の処理
if (isEmpty()) throw IndexOutOfBoundsException()
// 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換)
swap(0, size() - 1)
// ノードを削除
val _val = maxHeap.removeAt(size() - 1)
// 上から下へヒープ化
siftDown(0)
// ヒープ先頭要素を返す
return _val
}
/* ノード i から始めて、上から下へヒープ化 */
private fun siftDown(it: Int) {
// Kotlin の関数引数は不変のため、一時変数を作成する
var i = it
while (true) {
// ノード i, l, r のうち値が最大のノードを ma とする
val l = left(i)
val r = right(i)
var ma = i
if (l < size() && maxHeap[l] > maxHeap[ma]) ma = l
if (r < size() && maxHeap[r] > maxHeap[ma]) ma = r
// ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける
if (ma == i) break
// 2 つのノードを交換
swap(i, ma)
// ループで上から下へヒープ化
i = ma
}
}
/* ヒープ(二分木)を出力 */
fun print() {
val queue = PriorityQueue { a: Int, b: Int -> b - a }
queue.addAll(maxHeap)
printHeap(queue)
}
}
/* Driver Code */
fun main() {
/* 最大ヒープを初期化 */
val maxHeap = MaxHeap(mutableListOf(9, 8, 6, 6, 7, 5, 2, 1, 4, 3, 6, 2))
println("\n入力リストからヒープを構築した後")
maxHeap.print()
/* ヒープ頂点の要素を取得 */
var peek = maxHeap.peek()
print("\nヒープ先頭要素は $peek\n")
/* 要素をヒープに追加 */
val _val = 7
maxHeap.push(_val)
print("\n要素 $_val をヒープに追加した後\n")
maxHeap.print()
/* ヒープ頂点の要素を取り出す */
peek = maxHeap.pop()
print("\nヒープ先頭要素 $peek を取り出した後\n")
maxHeap.print()
/* ヒープのサイズを取得 */
val size = maxHeap.size()
print("\nヒープ要素数は $size\n")
/* ヒープが空かどうかを判定 */
val isEmpty = maxHeap.isEmpty()
print("\nヒープが空かどうか $isEmpty\n")
}
+38
View File
@@ -0,0 +1,38 @@
/**
* File: top_k.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_heap
import utils.printHeap
import java.util.*
/* ヒープに基づいて配列中の最大の k 個の要素を探す */
fun topKHeap(nums: IntArray, k: Int): Queue<Int> {
// 最小ヒープを初期化
val heap = PriorityQueue<Int>()
// 配列の先頭 k 個の要素をヒープに追加
for (i in 0..<k) {
heap.offer(nums[i])
}
// k+1 番目の要素から開始し、ヒープ長を k に保つ
for (i in k..<nums.size) {
// 現在の要素がヒープ先頭より大きければ、ヒープ先頭を取り出して現在の要素を追加する
if (nums[i] > heap.peek()) {
heap.poll()
heap.offer(nums[i])
}
}
return heap
}
/* Driver Code */
fun main() {
val nums = intArrayOf(1, 7, 6, 3, 2)
val k = 3
val res = topKHeap(nums, k)
println("最大の $k 個の要素は")
printHeap(res)
}
@@ -0,0 +1,59 @@
/**
* File: binary_search.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_searching
/* 二分探索(両閉区間) */
fun binarySearch(nums: IntArray, target: Int): Int {
// 両閉区間 [0, n-1] を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素を指す
var i = 0
var j = nums.size - 1
// ループし、探索区間が空になったら終了する(i > j で空)
while (i <= j) {
val m = i + (j - i) / 2 // 中点インデックス m を計算
if (nums[m] < target) // この場合、target は区間 [m+1, j] にある
i = m + 1
else if (nums[m] > target) // この場合、target は区間 [i, m-1] にある
j = m - 1
else // 目標要素が見つかったらそのインデックスを返す
return m
}
// 目標要素が見つからなければ -1 を返す
return -1
}
/* 二分探索(左閉右開区間) */
fun binarySearchLCRO(nums: IntArray, target: Int): Int {
// 左閉右開区間 [0, n) を初期化する。つまり i, j はそれぞれ配列の先頭要素と末尾要素+1を指す
var i = 0
var j = nums.size
// ループし、探索区間が空になったら終了する(i = j で空)
while (i < j) {
val m = i + (j - i) / 2 // 中点インデックス m を計算
if (nums[m] < target) // この場合、target は区間 [m+1, j) にある
i = m + 1
else if (nums[m] > target) // この場合、target は区間 [i, m) にある
j = m
else // 目標要素が見つかったらそのインデックスを返す
return m
}
// 目標要素が見つからなければ -1 を返す
return -1
}
/* Driver Code */
fun main() {
val target = 6
val nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35)
/* 二分探索(両閉区間) */
var index = binarySearch(nums, target)
println("対象要素 6 のインデックス = $index")
/* 二分探索(左閉右開区間) */
index = binarySearchLCRO(nums, target)
println("対象要素 6 のインデックス = $index")
}
@@ -0,0 +1,48 @@
/**
* File: binary_search_edge.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_searching
/* 最も左の target を二分探索 */
fun binarySearchLeftEdge(nums: IntArray, target: Int): Int {
// target の挿入位置を探すのと等価
val i = binarySearchInsertion(nums, target)
// target が見つからなければ、-1 を返す
if (i == nums.size || nums[i] != target) {
return -1
}
// target が見つかったら、インデックス i を返す
return i
}
/* 最も右の target を二分探索 */
fun binarySearchRightEdge(nums: IntArray, target: Int): Int {
// 最左の target + 1 を探す問題に変換する
val i = binarySearchInsertion(nums, target + 1)
// j は最も右の target を指し、i は target より大きい最初の要素を指す
val j = i - 1
// target が見つからなければ、-1 を返す
if (j == -1 || nums[j] != target) {
return -1
}
// target が見つかったら、インデックス j を返す
return j
}
/* Driver Code */
fun main() {
// 重複要素を含む配列
val nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15)
println("\n配列 nums = ${nums.contentToString()}")
// 二分探索で左端と右端を探す
for (target in intArrayOf(6, 7)) {
var index = binarySearchLeftEdge(nums, target)
println("最も左にある要素 $target のインデックスは $index")
index = binarySearchRightEdge(nums, target)
println("最も右にある要素 $target のインデックスは $index")
}
}
@@ -0,0 +1,65 @@
/**
* File: binary_search_insertion.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_searching
/* 二分探索で挿入位置を探す(重複要素なし) */
fun binarySearchInsertionSimple(nums: IntArray, target: Int): Int {
var i = 0
var j = nums.size - 1 // 両閉区間 [0, n-1] を初期化
while (i <= j) {
val m = i + (j - i) / 2 // 中点インデックス m を計算
if (nums[m] < target) {
i = m + 1 // target は区間 [m+1, j] にある
} else if (nums[m] > target) {
j = m - 1 // target は区間 [i, m-1] にある
} else {
return m // target が見つかったら、挿入位置 m を返す
}
}
// target が見つからなければ、挿入位置 i を返す
return i
}
/* 二分探索で挿入位置を探す(重複要素あり) */
fun binarySearchInsertion(nums: IntArray, target: Int): Int {
var i = 0
var j = nums.size - 1 // 両閉区間 [0, n-1] を初期化
while (i <= j) {
val m = i + (j - i) / 2 // 中点インデックス m を計算
if (nums[m] < target) {
i = m + 1 // target は区間 [m+1, j] にある
} else if (nums[m] > target) {
j = m - 1 // target は区間 [i, m-1] にある
} else {
j = m - 1 // target より小さい最初の要素は区間 [i, m-1] にある
}
}
// 挿入位置 i を返す
return i
}
/* Driver Code */
fun main() {
// 重複要素のない配列
var nums = intArrayOf(1, 3, 6, 8, 12, 15, 23, 26, 31, 35)
println("\n配列 nums = ${nums.contentToString()}")
// 二分探索で挿入位置を探す
for (target in intArrayOf(6, 9)) {
val index = binarySearchInsertionSimple(nums, target)
println("要素 $target の挿入位置のインデックスは $index")
}
// 重複要素を含む配列
nums = intArrayOf(1, 3, 6, 6, 6, 6, 6, 10, 12, 15)
println("\n配列 nums = ${nums.contentToString()}")
// 二分探索で挿入位置を探す
for (target in intArrayOf(2, 6, 20)) {
val index = binarySearchInsertion(nums, target)
println("要素 $target の挿入位置のインデックスは $index")
}
}
@@ -0,0 +1,49 @@
/**
* File: hashing_search.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_searching
import utils.ListNode
/* ハッシュ探索(配列) */
fun hashingSearchArray(map: Map<Int?, Int>, target: Int): Int {
// ハッシュテーブルの key: 対象要素、_val: インデックス
// ハッシュテーブルにその key がなければ -1 を返す
return map.getOrDefault(target, -1)
}
/* ハッシュ探索(連結リスト) */
fun hashingSearchLinkedList(map: Map<Int?, ListNode?>, target: Int): ListNode? {
// ハッシュテーブルの key: 対象ノードの値、_val: ノードオブジェクト
// ハッシュテーブルにその key がなければ null を返す
return map.getOrDefault(target, null)
}
/* Driver Code */
fun main() {
val target = 3
/* ハッシュ探索(配列) */
val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8)
// ハッシュテーブルを初期化
val map = HashMap<Int?, Int>()
for (i in nums.indices) {
map[nums[i]] = i // key: 要素、_val: インデックス
}
val index = hashingSearchArray(map, target)
println("目標要素 3 のインデックス = $index")
/* ハッシュ探索(連結リスト) */
var head = ListNode.arrToLinkedList(nums)
// ハッシュテーブルを初期化
val map1 = HashMap<Int?, ListNode?>()
while (head != null) {
map1[head._val] = head // key: ノード値、_val: ノード
head = head.next
}
val node = hashingSearchLinkedList(map1, target)
println("目標ノード値 3 に対応するノードオブジェクトは $node")
}
@@ -0,0 +1,50 @@
/**
* File: linear_search.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_searching
import utils.ListNode
/* 線形探索(配列) */
fun linearSearchArray(nums: IntArray, target: Int): Int {
// 配列を走査
for (i in nums.indices) {
// 目標要素が見つかったらそのインデックスを返す
if (nums[i] == target)
return i
}
// 目標要素が見つからなければ -1 を返す
return -1
}
/* 線形探索(連結リスト) */
fun linearSearchLinkedList(h: ListNode?, target: Int): ListNode? {
// 連結リストを走査
var head = h
while (head != null) {
// 対象ノードが見つかったら、それを返す
if (head._val == target)
return head
head = head.next
}
// 対象ノードが見つからない場合は null を返す
return null
}
/* Driver Code */
fun main() {
val target = 3
/* 配列で線形探索を行う */
val nums = intArrayOf(1, 5, 3, 2, 4, 7, 5, 9, 10, 8)
val index = linearSearchArray(nums, target)
println("目標要素 3 のインデックス = $index")
/* 連結リストで線形探索を行う */
val head = ListNode.arrToLinkedList(nums)
val node = linearSearchLinkedList(head, target)
println("目標ノード値 3 に対応するノードオブジェクトは $node")
}
@@ -0,0 +1,49 @@
/**
* File: two_sum.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_searching
/* 方法 1:総当たり列挙 */
fun twoSumBruteForce(nums: IntArray, target: Int): IntArray {
val size = nums.size
// 2重ループのため、時間計算量は O(n^2)
for (i in 0..<size - 1) {
for (j in i + 1..<size) {
if (nums[i] + nums[j] == target) return intArrayOf(i, j)
}
}
return IntArray(0)
}
/* 方法 2:補助ハッシュテーブル */
fun twoSumHashTable(nums: IntArray, target: Int): IntArray {
val size = nums.size
// 補助ハッシュテーブルを使用し、空間計算量は O(n)
val dic = HashMap<Int, Int>()
// 単一ループで、時間計算量は O(n)
for (i in 0..<size) {
if (dic.containsKey(target - nums[i])) {
return intArrayOf(dic[target - nums[i]]!!, i)
}
dic[nums[i]] = i
}
return IntArray(0)
}
/* Driver Code */
fun main() {
// ======= Test Case =======
val nums = intArrayOf(2, 7, 11, 15)
val target = 13
// ====== Driver Code ======
// 方法 1
var res = twoSumBruteForce(nums, target)
println("方法1 res = ${res.contentToString()}")
// 方法 2
res = twoSumHashTable(nums, target)
println("方法2 res = ${res.contentToString()}")
}
@@ -0,0 +1,53 @@
/**
* File: bubble_sort.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_sorting
/* バブルソート */
fun bubbleSort(nums: IntArray) {
// 外側のループ:未ソート区間は [0, i]
for (i in nums.size - 1 downTo 1) {
// 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
for (j in 0..<i) {
if (nums[j] > nums[j + 1]) {
// nums[j] と nums[j + 1] を交換
val temp = nums[j]
nums[j] = nums[j + 1]
nums[j + 1] = temp
}
}
}
}
/* バブルソート(フラグ最適化) */
fun bubbleSortWithFlag(nums: IntArray) {
// 外側のループ:未ソート区間は [0, i]
for (i in nums.size - 1 downTo 1) {
var flag = false // フラグを初期化する
// 内側のループ:未ソート区間 [0, i] の最大要素をその区間の最右端へ交換
for (j in 0..<i) {
if (nums[j] > nums[j + 1]) {
// nums[j] と nums[j + 1] を交換
val temp = nums[j]
nums[j] = nums[j + 1]
nums[j + 1] = temp
flag = true // 交換する要素を記録
}
}
if (!flag) break // このバブル処理で要素交換が一度もなければそのまま終了
}
}
/* Driver Code */
fun main() {
val nums = intArrayOf(4, 1, 3, 1, 5, 2)
bubbleSort(nums)
println("バブルソート完了後 nums = ${nums.contentToString()}")
val nums1 = intArrayOf(4, 1, 3, 1, 5, 2)
bubbleSortWithFlag(nums1)
println("バブルソート完了後 nums1 = ${nums1.contentToString()}")
}
@@ -0,0 +1,44 @@
/**
* File: bucket_sort.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_sorting
/* バケットソート */
fun bucketSort(nums: FloatArray) {
// k = n/2 個のバケットを初期化し、各バケットに 2 要素ずつ割り当てる想定とする
val k = nums.size / 2
val buckets = mutableListOf<MutableList<Float>>()
for (i in 0..<k) {
buckets.add(mutableListOf())
}
// 1. 配列要素を各バケットに振り分ける
for (num in nums) {
// 入力データの範囲は [0, 1) であり、num * k を用いてインデックス範囲 [0, k-1] に写像する
val i = (num * k).toInt()
// num をバケット i に追加
buckets[i].add(num)
}
// 2. 各バケットをソートする
for (bucket in buckets) {
// 組み込みのソート関数を使う。他のソートアルゴリズムに置き換えてもよい
bucket.sort()
}
// 3. バケットを走査して結果を結合
var i = 0
for (bucket in buckets) {
for (num in bucket) {
nums[i++] = num
}
}
}
/* Driver Code */
fun main() {
// 入力データは範囲 [0, 1) の浮動小数点数とする
val nums = floatArrayOf(0.49f, 0.96f, 0.82f, 0.09f, 0.57f, 0.43f, 0.91f, 0.75f, 0.15f, 0.37f)
bucketSort(nums)
println("バケットソート完了後 nums = ${nums.contentToString()}")
}
@@ -0,0 +1,80 @@
/**
* File: counting_sort.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_sorting
import kotlin.math.max
/* 計数ソート */
// 簡易実装のため、オブジェクトのソートには使えない
fun countingSortNaive(nums: IntArray) {
// 1. 配列の最大要素 m を求める
var m = 0
for (num in nums) {
m = max(m, num)
}
// 2. 各数値の出現回数を数える
// counter[num] は num の出現回数を表す
val counter = IntArray(m + 1)
for (num in nums) {
counter[num]++
}
// 3. counter を走査し、各要素を元の配列 nums に書き戻す
var i = 0
for (num in 0..<m + 1) {
var j = 0
while (j < counter[num]) {
nums[i] = num
j++
i++
}
}
}
/* 計数ソート */
// 完全な実装で、オブジェクトをソートでき、かつ安定ソートである
fun countingSort(nums: IntArray) {
// 1. 配列の最大要素 m を求める
var m = 0
for (num in nums) {
m = max(m, num)
}
// 2. 各数値の出現回数を数える
// counter[num] は num の出現回数を表す
val counter = IntArray(m + 1)
for (num in nums) {
counter[num]++
}
// 3. counter の累積和を求めて、「出現回数」を「末尾インデックス」に変換する
// つまり counter[num]-1 は、num が res に最後に現れるインデックス
for (i in 0..<m) {
counter[i + 1] += counter[i]
}
// 4. nums を逆順に走査し、各要素を結果配列 res に格納する
// 結果を記録するための配列 res を初期化
val n = nums.size
val res = IntArray(n)
for (i in n - 1 downTo 0) {
val num = nums[i]
res[counter[num] - 1] = num // num を対応するインデックスに配置
counter[num]-- // 累積和を 1 減らして、次に num を配置するインデックスを得る
}
// 結果配列 res で元の配列 nums を上書きする
for (i in 0..<n) {
nums[i] = res[i]
}
}
/* Driver Code */
fun main() {
val nums = intArrayOf(1, 0, 1, 2, 0, 4, 0, 2, 2, 4)
countingSortNaive(nums)
println("カウントソート(オブジェクトはソート不可)完了後 nums = ${nums.contentToString()}")
val nums1 = intArrayOf(1, 0, 1, 2, 0, 4, 0, 2, 2, 4)
countingSort(nums1)
println("カウントソート完了後 nums1 = ${nums1.contentToString()}")
}
@@ -0,0 +1,55 @@
/**
* File: heap_sort.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_sorting
/* ヒープの長さは n。ノード i から下方向にヒープ化 */
fun siftDown(nums: IntArray, n: Int, li: Int) {
var i = li
while (true) {
// ノード i, l, r のうち値が最大のノードを ma とする
val l = 2 * i + 1
val r = 2 * i + 2
var ma = i
if (l < n && nums[l] > nums[ma])
ma = l
if (r < n && nums[r] > nums[ma])
ma = r
// ノード i が最大、またはインデックス l, r が範囲外なら、ヒープ化は不要なので抜ける
if (ma == i)
break
// 2 つのノードを交換
val temp = nums[i]
nums[i] = nums[ma]
nums[ma] = temp
// ループで上から下へヒープ化
i = ma
}
}
/* ヒープソート */
fun heapSort(nums: IntArray) {
// ヒープ構築:葉ノード以外のすべてのノードをヒープ化する
for (i in nums.size / 2 - 1 downTo 0) {
siftDown(nums, nums.size, i)
}
// ヒープから最大要素を取り出し、n-1 回繰り返す
for (i in nums.size - 1 downTo 1) {
// 根ノードと最も右の葉ノードを交換(先頭要素と末尾要素を交換)
val temp = nums[0]
nums[0] = nums[i]
nums[i] = temp
// 根ノードを起点に、上から下へヒープ化
siftDown(nums, i, 0)
}
}
/* Driver Code */
fun main() {
val nums = intArrayOf(4, 1, 3, 1, 5, 2)
heapSort(nums)
println("ヒープソート完了後 nums = ${nums.contentToString()}")
}
@@ -0,0 +1,29 @@
/**
* File: insertion_sort.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_sorting
/* 挿入ソート */
fun insertionSort(nums: IntArray) {
// 外側ループ: ソート済み要素は 1, 2, ..., n
for (i in nums.indices) {
val base = nums[i]
var j = i - 1
// 内側ループ: base をソート済み区間 [0, i-1] の正しい位置に挿入する
while (j >= 0 && nums[j] > base) {
nums[j + 1] = nums[j] // nums[j] を 1 つ右へ移動する
j--
}
nums[j + 1] = base // base を正しい位置に配置する
}
}
/* Driver Code */
fun main() {
val nums = intArrayOf(4, 1, 3, 1, 5, 2)
insertionSort(nums)
println("挿入ソート完了後 nums = ${nums.contentToString()}")
}
@@ -0,0 +1,56 @@
/**
* File: merge_sort.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_sorting
/* 左部分配列と右部分配列をマージ */
fun merge(nums: IntArray, left: Int, mid: Int, right: Int) {
// 左部分配列の区間は [left, mid]、右部分配列の区間は [mid+1, right]
// マージ結果を格納する一時配列 tmp を作成
val tmp = IntArray(right - left + 1)
// 左右の部分配列の開始インデックスを初期化する
var i = left
var j = mid + 1
var k = 0
// 左右の部分配列にまだ要素がある間は比較し、小さいほうを一時配列にコピーする
while (i <= mid && j <= right) {
if (nums[i] <= nums[j])
tmp[k++] = nums[i++]
else
tmp[k++] = nums[j++]
}
// 左右の部分配列の残り要素を一時配列にコピーする
while (i <= mid) {
tmp[k++] = nums[i++]
}
while (j <= right) {
tmp[k++] = nums[j++]
}
// 一時配列 tmp の要素を元の配列 nums の対応区間にコピーする
for (l in tmp.indices) {
nums[left + l] = tmp[l]
}
}
/* マージソート */
fun mergeSort(nums: IntArray, left: Int, right: Int) {
// 終了条件
if (left >= right) return // 部分配列の長さが 1 になったら再帰を終了
// 分割フェーズ
val mid = left + (right - left) / 2 // 中点を計算
mergeSort(nums, left, mid) // 左部分配列を再帰処理
mergeSort(nums, mid + 1, right) // 右部分配列を再帰処理
// マージフェーズ
merge(nums, left, mid, right)
}
/* Driver Code */
fun main() {
/* マージソート */
val nums = intArrayOf(7, 3, 2, 6, 0, 1, 5, 4)
mergeSort(nums, 0, nums.size - 1)
println("マージソート完了後 nums = ${nums.contentToString()}")
}
@@ -0,0 +1,121 @@
/**
* File: quick_sort.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_sorting
/* 要素の交換 */
fun swap(nums: IntArray, i: Int, j: Int) {
val temp = nums[i]
nums[i] = nums[j]
nums[j] = temp
}
/* 番兵分割 */
fun partition(nums: IntArray, left: Int, right: Int): Int {
// nums[left] を基準値とする
var i = left
var j = right
while (i < j) {
while (i < j && nums[j] >= nums[left])
j-- // 右から左へ基準値未満の最初の要素を探す
while (i < j && nums[i] <= nums[left])
i++ // 左から右へ基準値より大きい最初の要素を探す
swap(nums, i, j) // この 2 つの要素を交換
}
swap(nums, i, left) // 基準値を 2 つの部分配列の境界へ交換する
return i // 基準値のインデックスを返す
}
/* クイックソート */
fun quickSort(nums: IntArray, left: Int, right: Int) {
// 部分配列の長さが 1 なら再帰を終了する
if (left >= right) return
// 番兵分割
val pivot = partition(nums, left, right)
// 左右の部分配列を再帰処理
quickSort(nums, left, pivot - 1)
quickSort(nums, pivot + 1, right)
}
/* 3つの候補要素の中央値を選ぶ */
fun medianThree(nums: IntArray, left: Int, mid: Int, right: Int): Int {
val l = nums[left]
val m = nums[mid]
val r = nums[right]
if ((m in l..r) || (m in r..l))
return mid // m は l と r の間
if ((l in m..r) || (l in r..m))
return left // l は m と r の間
return right
}
/* 番兵による分割処理(3 点中央値) */
fun partitionMedian(nums: IntArray, left: Int, right: Int): Int {
// 3つの候補要素の中央値を選ぶ
val med = medianThree(nums, left, (left + right) / 2, right)
// 中央値を配列の最左端に交換する
swap(nums, left, med)
// nums[left] を基準値とする
var i = left
var j = right
while (i < j) {
while (i < j && nums[j] >= nums[left])
j-- // 右から左へ基準値未満の最初の要素を探す
while (i < j && nums[i] <= nums[left])
i++ // 左から右へ基準値より大きい最初の要素を探す
swap(nums, i, j) // この 2 つの要素を交換
}
swap(nums, i, left) // 基準値を 2 つの部分配列の境界へ交換する
return i // 基準値のインデックスを返す
}
/* クイックソート */
fun quickSortMedian(nums: IntArray, left: Int, right: Int) {
// 部分配列の長さが 1 なら再帰を終了する
if (left >= right) return
// 番兵分割
val pivot = partitionMedian(nums, left, right)
// 左右の部分配列を再帰処理
quickSort(nums, left, pivot - 1)
quickSort(nums, pivot + 1, right)
}
/* クイックソート(再帰深度最適化) */
fun quickSortTailCall(nums: IntArray, left: Int, right: Int) {
// 部分配列の長さが 1 なら終了
var l = left
var r = right
while (l < r) {
// 番兵による分割処理
val pivot = partition(nums, l, r)
// 2 つの部分配列のうち短いほうにクイックソートを適用する
if (pivot - l < r - pivot) {
quickSort(nums, l, pivot - 1) // 左部分配列を再帰的にソート
l = pivot + 1 // 未ソート区間の残りは [pivot + 1, right]
} else {
quickSort(nums, pivot + 1, r) // 右部分配列を再帰的にソート
r = pivot - 1 // 未ソート区間の残りは [left, pivot - 1]
}
}
}
/* Driver Code */
fun main() {
/* クイックソート */
val nums = intArrayOf(2, 4, 1, 0, 3, 5)
quickSort(nums, 0, nums.size - 1)
println("クイックソート完了後 nums = ${nums.contentToString()}")
/* クイックソート(中央値の基準値で最適化) */
val nums1 = intArrayOf(2, 4, 1, 0, 3, 5)
quickSortMedian(nums1, 0, nums1.size - 1)
println("クイックソート(中央値ピボット最適化)完了後 nums1 = ${nums1.contentToString()}")
/* クイックソート(再帰深度最適化) */
val nums2 = intArrayOf(2, 4, 1, 0, 3, 5)
quickSortTailCall(nums2, 0, nums2.size - 1)
println("クイックソート(再帰深度最適化)完了後 nums2 = ${nums2.contentToString()}")
}
@@ -0,0 +1,68 @@
/**
* File: radix_sort.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_sorting
/* 要素 num の下から k 桁目を取得(exp = 10^(k-1) */
fun digit(num: Int, exp: Int): Int {
// ここで高コストな累乗計算を繰り返さないよう、k ではなく exp を渡す
return (num / exp) % 10
}
/* 計数ソート(nums の k 桁目でソート) */
fun countingSortDigit(nums: IntArray, exp: Int) {
// 10 進数の各桁は 0~9 の範囲なので、長さ 10 のバケット配列が必要
val counter = IntArray(10)
val n = nums.size
// 0~9 の各数字の出現回数を集計する
for (i in 0..<n) {
val d = digit(nums[i], exp) // nums[i] の第 k 位を取得し、d とする
counter[d]++ // 数字 d の出現回数を数える
}
// 累積和を求め、「出現回数」を「配列インデックス」に変換する
for (i in 1..9) {
counter[i] += counter[i - 1]
}
// 逆順に走査し、バケット内の集計結果に従って各要素を res に格納する
val res = IntArray(n)
for (i in n - 1 downTo 0) {
val d = digit(nums[i], exp)
val j = counter[d] - 1 // d の配列内インデックス j を取得する
res[j] = nums[i] // 現在の要素をインデックス j に格納する
counter[d]-- // d の個数を 1 減らす
}
// 結果で元の配列 nums を上書きする
for (i in 0..<n)
nums[i] = res[i]
}
/* 基数ソート */
fun radixSort(nums: IntArray) {
// 最大桁数の判定用に配列の最大要素を取得
var m = Int.MIN_VALUE
for (num in nums) if (num > m) m = num
var exp = 1
// 下位桁から上位桁の順に走査する
while (exp <= m) {
// 配列要素の k 桁目に対して計数ソートを行う
// k = 1 -> exp = 1
// k = 2 -> exp = 10
// つまり exp = 10^(k-1)
countingSortDigit(nums, exp)
exp *= 10
}
}
/* Driver Code */
fun main() {
// 基数ソート
val nums = intArrayOf(
10546151, 35663510, 42865989, 34862445, 81883077,
88906420, 72429244, 30524779, 82060337, 63832996
)
radixSort(nums)
println("基数ソート完了後 nums = ${nums.contentToString()}")
}
@@ -0,0 +1,32 @@
/**
* File: selection_sort.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_sorting
/* 選択ソート */
fun selectionSort(nums: IntArray) {
val n = nums.size
// 外側ループ:未整列区間は [i, n-1]
for (i in 0..<n - 1) {
var k = i
// 内側のループ:未ソート区間の最小要素を見つける
for (j in i + 1..<n) {
if (nums[j] < nums[k])
k = j // 最小要素のインデックスを記録
}
// その最小要素を未整列区間の先頭要素と交換する
val temp = nums[i]
nums[i] = nums[k]
nums[k] = temp
}
}
/* Driver Code */
fun main() {
val nums = intArrayOf(4, 1, 3, 1, 5, 2)
selectionSort(nums)
println("選択ソート完了後 nums = ${nums.contentToString()}")
}
@@ -0,0 +1,145 @@
/**
* File: array_deque.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_stack_and_queue
/* 循環配列ベースの両端キュー */
/* コンストラクタ */
class ArrayDeque(capacity: Int) {
private var nums: IntArray = IntArray(capacity) // 両端キューの要素を格納する配列
private var front: Int = 0 // 先頭ポインタ。先頭要素を指す
private var queSize: Int = 0 // 両端キューの長さ
/* 両端キューの容量を取得 */
fun capacity(): Int {
return nums.size
}
/* 両端キューの長さを取得 */
fun size(): Int {
return queSize
}
/* 両端キューが空かどうかを判定 */
fun isEmpty(): Boolean {
return queSize == 0
}
/* 循環配列のインデックスを計算 */
private fun index(i: Int): Int {
// 剰余演算により配列の先頭と末尾をつなげる
// i が配列の末尾を越えたら先頭に戻る
// i が配列の先頭を越えて前に出たら末尾に戻る
return (i + capacity()) % capacity()
}
/* キュー先頭にエンキュー */
fun pushFirst(num: Int) {
if (queSize == capacity()) {
println("双方向キューは満杯です")
return
}
// 先頭ポインタを左に 1 つ移動する
// 剰余演算により、front が配列先頭を越えた後に末尾へ戻るようにする
front = index(front - 1)
// num をキュー先頭に追加
nums[front] = num
queSize++
}
/* キュー末尾にエンキュー */
fun pushLast(num: Int) {
if (queSize == capacity()) {
println("双方向キューは満杯です")
return
}
// キュー末尾ポインタを計算し、末尾インデックス + 1 を指す
val rear = index(front + queSize)
// num をキュー末尾に追加
nums[rear] = num
queSize++
}
/* キュー先頭からデキュー */
fun popFirst(): Int {
val num = peekFirst()
// 先頭ポインタを 1 つ後ろへ進める
front = index(front + 1)
queSize--
return num
}
/* キュー末尾からデキュー */
fun popLast(): Int {
val num = peekLast()
queSize--
return num
}
/* キュー先頭の要素にアクセス */
fun peekFirst(): Int {
if (isEmpty()) throw IndexOutOfBoundsException()
return nums[front]
}
/* キュー末尾の要素にアクセス */
fun peekLast(): Int {
if (isEmpty()) throw IndexOutOfBoundsException()
// 末尾要素のインデックスを計算
val last = index(front + queSize - 1)
return nums[last]
}
/* 出力用の配列を返す */
fun toArray(): IntArray {
// 有効長の範囲内のリスト要素のみを変換
val res = IntArray(queSize)
var i = 0
var j = front
while (i < queSize) {
res[i] = nums[index(j)]
i++
j++
}
return res
}
}
/* Driver Code */
fun main() {
/* 両端キューを初期化 */
val deque = ArrayDeque(10)
deque.pushLast(3)
deque.pushLast(2)
deque.pushLast(5)
println("双方向キュー deque = ${deque.toArray().contentToString()}")
/* 要素にアクセス */
val peekFirst = deque.peekFirst()
println("先頭要素 peekFirst = $peekFirst")
val peekLast = deque.peekLast()
println("末尾要素 peekLast = $peekLast")
/* 要素をエンキュー */
deque.pushLast(4)
println("要素 4 を末尾に追加した後 deque = ${deque.toArray().contentToString()}")
deque.pushFirst(1)
println("要素 1 を先頭に追加した後 deque = ${deque.toArray().contentToString()}")
/* 要素をデキュー */
val popLast = deque.popLast()
println("末尾から取り出した要素 = ${popLast}、末尾から取り出した後 deque = ${deque.toArray().contentToString()}")
val popFirst = deque.popFirst()
println("先頭から取り出した要素 = ${popFirst}、先頭から取り出した後 deque = ${deque.toArray().contentToString()}")
/* 両端キューの長さを取得 */
val size = deque.size()
println("双方向キューの長さ size = $size")
/* 両端キューが空かどうかを判定 */
val isEmpty = deque.isEmpty()
println("双方向キューが空かどうか = $isEmpty")
}
@@ -0,0 +1,110 @@
/**
* File: array_queue.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_stack_and_queue
/* 循環配列ベースのキュー */
class ArrayQueue(capacity: Int) {
private val nums: IntArray = IntArray(capacity) // キュー要素を格納する配列
private var front: Int = 0 // 先頭ポインタ。先頭要素を指す
private var queSize: Int = 0 // キューの長さ
/* キューの容量を取得 */
fun capacity(): Int {
return nums.size
}
/* キューの長さを取得 */
fun size(): Int {
return queSize
}
/* キューが空かどうかを判定 */
fun isEmpty(): Boolean {
return queSize == 0
}
/* エンキュー */
fun push(num: Int) {
if (queSize == capacity()) {
println("キューは満杯です")
return
}
// 末尾ポインタを計算し、末尾インデックス + 1 を指す
// 剰余演算により、rear が配列末尾を越えた後に先頭へ戻るようにする
val rear = (front + queSize) % capacity()
// num をキュー末尾に追加
nums[rear] = num
queSize++
}
/* デキュー */
fun pop(): Int {
val num = peek()
// 先頭ポインタを1つ後ろへ進め、末尾を越えたら配列先頭に戻す
front = (front + 1) % capacity()
queSize--
return num
}
/* キュー先頭の要素にアクセス */
fun peek(): Int {
if (isEmpty()) throw IndexOutOfBoundsException()
return nums[front]
}
/* 配列を返す */
fun toArray(): IntArray {
// 有効長の範囲内のリスト要素のみを変換
val res = IntArray(queSize)
var i = 0
var j = front
while (i < queSize) {
res[i] = nums[j % capacity()]
i++
j++
}
return res
}
}
/* Driver Code */
fun main() {
/* キューを初期化 */
val capacity = 10
val queue = ArrayQueue(capacity)
/* 要素をエンキュー */
queue.push(1)
queue.push(3)
queue.push(2)
queue.push(5)
queue.push(4)
println("キュー queue = ${queue.toArray().contentToString()}")
/* キュー先頭の要素にアクセス */
val peek = queue.peek()
println("先頭要素 peek = $peek")
/* 要素をデキュー */
val pop = queue.pop()
println("取り出した要素 pop = ${pop}、取り出した後 queue = ${queue.toArray().contentToString()}")
/* キューの長さを取得 */
val size = queue.size()
println("キューの長さ size = $size")
/* キューが空かどうかを判定 */
val isEmpty = queue.isEmpty()
println("キューが空かどうか = $isEmpty")
/* 循環配列をテストする */
for (i in 0..9) {
queue.push(i)
queue.pop()
println("$i ラウンドの追加 + 取り出し後 queue = ${queue.toArray().contentToString()}")
}
}
@@ -0,0 +1,75 @@
/**
* File: array_stack.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_stack_and_queue
/* 配列ベースのスタック */
class ArrayStack {
// リスト(動的配列)を初期化する
private val stack = mutableListOf<Int>()
/* スタックの長さを取得 */
fun size(): Int {
return stack.size
}
/* スタックが空かどうかを判定 */
fun isEmpty(): Boolean {
return size() == 0
}
/* プッシュ */
fun push(num: Int) {
stack.add(num)
}
/* ポップ */
fun pop(): Int {
if (isEmpty()) throw IndexOutOfBoundsException()
return stack.removeAt(size() - 1)
}
/* スタックトップの要素にアクセス */
fun peek(): Int {
if (isEmpty()) throw IndexOutOfBoundsException()
return stack[size() - 1]
}
/* List を Array に変換して返す */
fun toArray(): Array<Any> {
return stack.toTypedArray()
}
}
/* Driver Code */
fun main() {
/* スタックを初期化 */
val stack = ArrayStack()
/* 要素をプッシュ */
stack.push(1)
stack.push(3)
stack.push(2)
stack.push(5)
stack.push(4)
println("スタック stack = ${stack.toArray().contentToString()}")
/* スタックトップの要素にアクセス */
val peek = stack.peek()
println("スタックトップ要素 peek = $peek")
/* 要素をポップ */
val pop = stack.pop()
println("ポップした要素 pop = $pop、ポップ後の stack = ${stack.toArray().contentToString()}")
/* スタックの長さを取得 */
val size = stack.size()
println("スタックの長さ size = $size")
/* 空かどうかを判定 */
val isEmpty = stack.isEmpty()
println("スタックが空かどうか = $isEmpty")
}
@@ -0,0 +1,45 @@
/**
* File: deque.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_stack_and_queue
import java.util.*
/* Driver Code */
fun main() {
/* 両端キューを初期化 */
val deque = LinkedList<Int>()
deque.offerLast(3)
deque.offerLast(2)
deque.offerLast(5)
println("両端キュー deque = $deque")
/* 要素にアクセス */
val peekFirst = deque.peekFirst()
println("先頭要素 peekFirst = $peekFirst")
val peekLast = deque.peekLast()
println("末尾要素 peekLast = $peekLast")
/* 要素をエンキュー */
deque.offerLast(4)
println("要素 4 を末尾に追加後 deque = $deque")
deque.offerFirst(1)
println("要素 1 を先頭に追加後 deque = $deque")
/* 要素をデキュー */
val popLast = deque.pollLast()
println("末尾から取り出した要素 = $popLast、取り出し後 deque = $deque")
val popFirst = deque.pollFirst()
println("先頭から取り出した要素 = $popFirst、取り出し後 deque = $deque")
/* 両端キューの長さを取得 */
val size = deque.size
println("双方向キューの長さ size = $size")
/* 両端キューが空かどうかを判定 */
val isEmpty = deque.isEmpty()
println("双方向キューが空かどうか = $isEmpty")
}
@@ -0,0 +1,163 @@
/**
* File: linkedlist_deque.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_stack_and_queue
/* 双方向連結リストノード */
class ListNode(var _val: Int) {
// ノード値
var next: ListNode? = null // 後続ノードへの参照
var prev: ListNode? = null // 前駆ノードへの参照
}
/* 双方向連結リストベースの両端キュー */
class LinkedListDeque {
private var front: ListNode? = null // 先頭ノード front
private var rear: ListNode? = null // 末尾ノード rear
private var queSize: Int = 0 // 両端キューの長さ
/* 両端キューの長さを取得 */
fun size(): Int {
return queSize
}
/* 両端キューが空かどうかを判定 */
fun isEmpty(): Boolean {
return size() == 0
}
/* エンキュー操作 */
fun push(num: Int, isFront: Boolean) {
val node = ListNode(num)
// 連結リストが空なら、front と rear の両方を node に向ける
if (isEmpty()) {
rear = node
front = rear
// 先頭へのエンキュー操作
} else if (isFront) {
// node を連結リストの先頭に追加
front?.prev = node
node.next = front
front = node // 先頭ノードを更新する
// 末尾へのエンキュー操作
} else {
// node を連結リストの末尾に追加
rear?.next = node
node.prev = rear
rear = node // 末尾ノードを更新する
}
queSize++ // キューの長さを更新
}
/* キュー先頭にエンキュー */
fun pushFirst(num: Int) {
push(num, true)
}
/* キュー末尾にエンキュー */
fun pushLast(num: Int) {
push(num, false)
}
/* デキュー操作 */
fun pop(isFront: Boolean): Int {
if (isEmpty())
throw IndexOutOfBoundsException()
val _val: Int
// キュー先頭からの取り出し
if (isFront) {
_val = front!!._val // 先頭ノードの値を一時保存
// 先頭ノードを削除
val fNext = front!!.next
if (fNext != null) {
fNext.prev = null
front!!.next = null
}
front = fNext // 先頭ノードを更新する
// キュー末尾からの取り出し
} else {
_val = rear!!._val // 末尾ノードの値を一時保存
// 末尾ノードを削除
val rPrev = rear!!.prev
if (rPrev != null) {
rPrev.next = null
rear!!.prev = null
}
rear = rPrev // 末尾ノードを更新する
}
queSize-- // キューの長さを更新
return _val
}
/* キュー先頭からデキュー */
fun popFirst(): Int {
return pop(true)
}
/* キュー末尾からデキュー */
fun popLast(): Int {
return pop(false)
}
/* キュー先頭の要素にアクセス */
fun peekFirst(): Int {
if (isEmpty()) throw IndexOutOfBoundsException()
return front!!._val
}
/* キュー末尾の要素にアクセス */
fun peekLast(): Int {
if (isEmpty()) throw IndexOutOfBoundsException()
return rear!!._val
}
/* 出力用の配列を返す */
fun toArray(): IntArray {
var node = front
val res = IntArray(size())
for (i in res.indices) {
res[i] = node!!._val
node = node.next
}
return res
}
}
/* Driver Code */
fun main() {
/* 両端キューを初期化 */
val deque = LinkedListDeque()
deque.pushLast(3)
deque.pushLast(2)
deque.pushLast(5)
println("双方向キュー deque = ${deque.toArray().contentToString()}")
/* 要素にアクセス */
val peekFirst = deque.peekFirst()
println("先頭要素 peekFirst = $peekFirst")
val peekLast = deque.peekLast()
println("末尾要素 peekLast = $peekLast")
/* 要素をエンキュー */
deque.pushLast(4)
println("要素 4 を末尾に追加した後 deque = ${deque.toArray().contentToString()}")
deque.pushFirst(1)
println("要素 1 を先頭に追加した後 deque = ${deque.toArray().contentToString()}")
/* 要素をデキュー */
val popLast = deque.popLast()
println("末尾から取り出した要素 = ${popLast}、末尾から取り出した後 deque = ${deque.toArray().contentToString()}")
val popFirst = deque.popFirst()
println("先頭から取り出した要素 = ${popFirst}、先頭から取り出した後 deque = ${deque.toArray().contentToString()}")
/* 両端キューの長さを取得 */
val size = deque.size()
println("双方向キューの長さ size = $size")
/* 両端キューが空かどうかを判定 */
val isEmpty = deque.isEmpty()
println("双方向キューが空かどうか = $isEmpty")
}
@@ -0,0 +1,98 @@
/**
* File: linkedlist_queue.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_stack_and_queue
/* 連結リストベースのキュー */
class LinkedListQueue(
// 先頭ノード front、末尾ノード rear
private var front: ListNode? = null,
private var rear: ListNode? = null,
private var queSize: Int = 0
) {
/* キューの長さを取得 */
fun size(): Int {
return queSize
}
/* キューが空かどうかを判定 */
fun isEmpty(): Boolean {
return size() == 0
}
/* エンキュー */
fun push(num: Int) {
// 末尾ノードの後ろに num を追加
val node = ListNode(num)
// キューが空なら、先頭・末尾ノードをともにそのノードに設定
if (front == null) {
front = node
rear = node
// キューが空でなければ、そのノードを末尾ノードの後ろに追加
} else {
rear?.next = node
rear = node
}
queSize++
}
/* デキュー */
fun pop(): Int {
val num = peek()
// 先頭ノードを削除
front = front?.next
queSize--
return num
}
/* キュー先頭の要素にアクセス */
fun peek(): Int {
if (isEmpty()) throw IndexOutOfBoundsException()
return front!!._val
}
/* 連結リストを Array に変換して返す */
fun toArray(): IntArray {
var node = front
val res = IntArray(size())
for (i in res.indices) {
res[i] = node!!._val
node = node.next
}
return res
}
}
/* Driver Code */
fun main() {
/* キューを初期化 */
val queue = LinkedListQueue()
/* 要素をエンキュー */
queue.push(1)
queue.push(3)
queue.push(2)
queue.push(5)
queue.push(4)
println("キュー queue = ${queue.toArray().contentToString()}")
/* キュー先頭の要素にアクセス */
val peek = queue.peek()
println("先頭要素 peek = $peek")
/* 要素をデキュー */
val pop = queue.pop()
println("デキューした要素 pop = $pop、デキュー後 queue = ${queue.toArray().contentToString()}")
/* キューの長さを取得 */
val size = queue.size()
println("キューの長さ size = $size")
/* キューが空かどうかを判定 */
val isEmpty = queue.isEmpty()
println("キューが空かどうか = $isEmpty")
}
@@ -0,0 +1,87 @@
/**
* File: linkedlist_stack.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_stack_and_queue
/* 連結リストベースのスタック */
class LinkedListStack(
private var stackPeek: ListNode? = null, // 先頭ノードをスタックトップとする
private var stkSize: Int = 0 // スタックの長さ
) {
/* スタックの長さを取得 */
fun size(): Int {
return stkSize
}
/* スタックが空かどうかを判定 */
fun isEmpty(): Boolean {
return size() == 0
}
/* プッシュ */
fun push(num: Int) {
val node = ListNode(num)
node.next = stackPeek
stackPeek = node
stkSize++
}
/* ポップ */
fun pop(): Int? {
val num = peek()
stackPeek = stackPeek?.next
stkSize--
return num
}
/* スタックトップの要素にアクセス */
fun peek(): Int? {
if (isEmpty()) throw IndexOutOfBoundsException()
return stackPeek?._val
}
/* List を Array に変換して返す */
fun toArray(): IntArray {
var node = stackPeek
val res = IntArray(size())
for (i in res.size - 1 downTo 0) {
res[i] = node?._val!!
node = node.next
}
return res
}
}
/* Driver Code */
fun main() {
/* スタックを初期化 */
val stack = LinkedListStack()
/* 要素をプッシュ */
stack.push(1)
stack.push(3)
stack.push(2)
stack.push(5)
stack.push(4)
println("スタック stack = ${stack.toArray().contentToString()}")
/* スタックトップの要素にアクセス */
val peek = stack.peek()!!
println("スタックトップ要素 peek = $peek")
/* 要素をポップ */
val pop = stack.pop()!!
println("ポップした要素 pop = $pop、ポップ後の stack = ${stack.toArray().contentToString()}")
/* スタックの長さを取得 */
val size = stack.size()
println("スタックの長さ size = $size")
/* 空かどうかを判定 */
val isEmpty = stack.isEmpty()
println("スタックが空かどうか = $isEmpty")
}
@@ -0,0 +1,39 @@
/**
* File: queue.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_stack_and_queue
import java.util.*
/* Driver Code */
fun main() {
/* キューを初期化 */
val queue = LinkedList<Int>()
/* 要素をエンキュー */
queue.offer(1)
queue.offer(3)
queue.offer(2)
queue.offer(5)
queue.offer(4)
println("キュー queue = $queue")
/* キュー先頭の要素にアクセス */
val peek = queue.peek()
println("先頭要素 peek = $peek")
/* 要素をデキュー */
val pop = queue.poll()
println("デキューした要素 pop = $pop、デキュー後 queue = $queue")
/* キューの長さを取得 */
val size = queue.size
println("キューの長さ size = $size")
/* キューが空かどうかを判定 */
val isEmpty = queue.isEmpty()
println("キューが空かどうか = $isEmpty")
}
@@ -0,0 +1,39 @@
/**
* File: stack.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_stack_and_queue
import java.util.*
/* Driver Code */
fun main() {
/* スタックを初期化 */
val stack = Stack<Int>()
/* 要素をプッシュ */
stack.push(1)
stack.push(3)
stack.push(2)
stack.push(5)
stack.push(4)
println("スタック stack = $stack")
/* スタックトップの要素にアクセス */
val peek = stack.peek()
println("スタックトップ要素 peek = $peek")
/* 要素をポップ */
val pop = stack.pop()
println("ポップした要素 pop = $pop、ポップ後 stack = $stack")
/* スタックの長さを取得 */
val size = stack.size
println("スタックの長さ size = $size")
/* 空かどうかを判定 */
val isEmpty = stack.isEmpty()
println("スタックが空かどうか = $isEmpty")
}
@@ -0,0 +1,127 @@
/**
* File: array_binary_tree.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_tree
import utils.TreeNode
import utils.printTree
/* 配列表現による二分木クラス */
class ArrayBinaryTree(private val tree: MutableList<Int?>) {
/* リスト容量 */
fun size(): Int {
return tree.size
}
/* インデックス i のノードの値を取得 */
fun _val(i: Int): Int? {
// インデックスが範囲外なら、空きを表す null を返す
if (i < 0 || i >= size()) return null
return tree[i]
}
/* インデックス i のノードの左子ノードのインデックスを取得 */
fun left(i: Int): Int {
return 2 * i + 1
}
/* インデックス i のノードの右子ノードのインデックスを取得 */
fun right(i: Int): Int {
return 2 * i + 2
}
/* インデックス i のノードの親ノードのインデックスを取得 */
fun parent(i: Int): Int {
return (i - 1) / 2
}
/* レベル順走査 */
fun levelOrder(): MutableList<Int?> {
val res = mutableListOf<Int?>()
// 配列を直接走査する
for (i in 0..<size()) {
if (_val(i) != null)
res.add(_val(i))
}
return res
}
/* 深さ優先探索 */
fun dfs(i: Int, order: String, res: MutableList<Int?>) {
// 空きスロットなら返す
if (_val(i) == null)
return
// 先行順走査
if ("pre" == order)
res.add(_val(i))
dfs(left(i), order, res)
// 中順走査
if ("in" == order)
res.add(_val(i))
dfs(right(i), order, res)
// 後順走査
if ("post" == order)
res.add(_val(i))
}
/* 先行順走査 */
fun preOrder(): MutableList<Int?> {
val res = mutableListOf<Int?>()
dfs(0, "pre", res)
return res
}
/* 中順走査 */
fun inOrder(): MutableList<Int?> {
val res = mutableListOf<Int?>()
dfs(0, "in", res)
return res
}
/* 後順走査 */
fun postOrder(): MutableList<Int?> {
val res = mutableListOf<Int?>()
dfs(0, "post", res)
return res
}
}
/* Driver Code */
fun main() {
// 二分木を初期化する
// ここでは、リストから二分木を直接生成する関数を利用する
val arr = mutableListOf(1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15)
val root = TreeNode.listToTree(arr)
println("\n二分木を初期化\n")
println("二分木の配列表現:")
println(arr)
println("二分木の連結リスト表現:")
printTree(root)
// 配列表現による二分木クラス
val abt = ArrayBinaryTree(arr)
// ノードにアクセス
val i = 1
val l = abt.left(i)
val r = abt.right(i)
val p = abt.parent(i)
println("現在のノードのインデックスは $i 、値は ${abt._val(i)}")
println("左の子ノードのインデックスは $l 、値は ${abt._val(l)}")
println("右の子ノードのインデックスは $r 、値は ${abt._val(r)}")
println("親ノードのインデックスは $p 、値は ${abt._val(p)}")
// 木を走査
var res = abt.levelOrder()
println("\nレベル順走査:$res")
res = abt.preOrder()
println("先行順走査:$res")
res = abt.inOrder()
println("中間順走査:$res")
res = abt.postOrder()
println("後行順走査:$res")
}
+223
View File
@@ -0,0 +1,223 @@
/**
* File: avl_tree.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_tree
import utils.TreeNode
import utils.printTree
import kotlin.math.max
/* AVL 木 */
class AVLTree {
var root: TreeNode? = null // 根ノード
/* ノードの高さを取得 */
fun height(node: TreeNode?): Int {
// 空ノードの高さは -1、葉ノードの高さは 0
return node?.height ?: -1
}
/* ノードの高さを更新する */
private fun updateHeight(node: TreeNode?) {
// ノードの高さは最も高い部分木の高さ + 1 に等しい
node?.height = max(height(node?.left), height(node?.right)) + 1
}
/* 平衡係数を取得 */
fun balanceFactor(node: TreeNode?): Int {
// 空ノードの平衡係数は 0
if (node == null) return 0
// ノードの平衡係数 = 左部分木の高さ - 右部分木の高さ
return height(node.left) - height(node.right)
}
/* 右回転 */
private fun rightRotate(node: TreeNode?): TreeNode {
val child = node!!.left
val grandChild = child!!.right
// child を支点として node を右回転させる
child.right = node
node.left = grandChild
// ノードの高さを更新する
updateHeight(node)
updateHeight(child)
// 回転後の部分木の根ノードを返す
return child
}
/* 左回転 */
private fun leftRotate(node: TreeNode?): TreeNode {
val child = node!!.right
val grandChild = child!!.left
// child を支点として node を左回転させる
child.left = node
node.right = grandChild
// ノードの高さを更新する
updateHeight(node)
updateHeight(child)
// 回転後の部分木の根ノードを返す
return child
}
/* 回転操作を行い、この部分木の平衡を回復する */
private fun rotate(node: TreeNode): TreeNode {
// ノード node の平衡係数を取得
val balanceFactor = balanceFactor(node)
// 左に偏った木
if (balanceFactor > 1) {
if (balanceFactor(node.left) >= 0) {
// 右回転
return rightRotate(node)
} else {
// 左回転してから右回転
node.left = leftRotate(node.left)
return rightRotate(node)
}
}
// 右に偏った木
if (balanceFactor < -1) {
if (balanceFactor(node.right) <= 0) {
// 左回転
return leftRotate(node)
} else {
// 右回転してから左回転
node.right = rightRotate(node.right)
return leftRotate(node)
}
}
// 平衡木なので回転不要、そのまま返す
return node
}
/* ノードを挿入 */
fun insert(_val: Int) {
root = insertHelper(root, _val)
}
/* ノードを再帰的に挿入する(補助メソッド) */
private fun insertHelper(n: TreeNode?, _val: Int): TreeNode {
if (n == null)
return TreeNode(_val)
var node = n
/* 1. 挿入位置を探索してノードを挿入 */
if (_val < node._val)
node.left = insertHelper(node.left, _val)
else if (_val > node._val)
node.right = insertHelper(node.right, _val)
else
return node // 重複ノードは挿入せず、そのまま返す
updateHeight(node) // ノードの高さを更新する
/* 2. 回転操作を行い、部分木の平衡を回復する */
node = rotate(node)
// 部分木の根ノードを返す
return node
}
/* ノードを削除 */
fun remove(_val: Int) {
root = removeHelper(root, _val)
}
/* ノードを再帰的に削除する(補助メソッド) */
private fun removeHelper(n: TreeNode?, _val: Int): TreeNode? {
var node = n ?: return null
/* 1. ノードを探索して削除 */
if (_val < node._val)
node.left = removeHelper(node.left, _val)
else if (_val > node._val)
node.right = removeHelper(node.right, _val)
else {
if (node.left == null || node.right == null) {
val child = if (node.left != null)
node.left
else
node.right
// 子ノード数 = 0 の場合、node をそのまま削除して返す
if (child == null)
return null
// 子ノード数 = 1 の場合、node をそのまま削除する
else
node = child
} else {
// 子ノード数 = 2 の場合、中順走査の次のノードを削除し、そのノードで現在のノードを置き換える
var temp = node.right
while (temp!!.left != null) {
temp = temp.left
}
node.right = removeHelper(node.right, temp._val)
node._val = temp._val
}
}
updateHeight(node) // ノードの高さを更新する
/* 2. 回転操作を行い、部分木の平衡を回復する */
node = rotate(node)
// 部分木の根ノードを返す
return node
}
/* ノードを探索 */
fun search(_val: Int): TreeNode? {
var cur = root
// ループで探索し、葉ノードを越えたら抜ける
while (cur != null) {
// 目標ノードは cur の右部分木にある
cur = if (cur._val < _val)
cur.right!!
// 目標ノードは cur の左部分木にある
else if (cur._val > _val)
cur.left
// 目標ノードが見つかったらループを抜ける
else
break
}
// 目標ノードを返す
return cur
}
}
fun testInsert(tree: AVLTree, _val: Int) {
tree.insert(_val)
println("\nノード $_val を挿入後、AVL 木は")
printTree(tree.root)
}
fun testRemove(tree: AVLTree, _val: Int) {
tree.remove(_val)
println("\nノード $_val を削除後、AVL 木は")
printTree(tree.root)
}
/* Driver Code */
fun main() {
/* 空の AVL 木を初期化する */
val avlTree = AVLTree()
/* ノードを挿入 */
// ノード挿入後に AVL 木がどのように平衡を保つかに注目してほしい
testInsert(avlTree, 1)
testInsert(avlTree, 2)
testInsert(avlTree, 3)
testInsert(avlTree, 4)
testInsert(avlTree, 5)
testInsert(avlTree, 8)
testInsert(avlTree, 7)
testInsert(avlTree, 9)
testInsert(avlTree, 10)
testInsert(avlTree, 6)
/* 重複ノードを挿入する */
testInsert(avlTree, 7)
/* ノードを削除 */
// ノード削除後に AVL 木がどのように平衡を保つかに注目してほしい
testRemove(avlTree, 8) // 次数 0 のノードを削除する
testRemove(avlTree, 5) // 次数 1 のノードを削除する
testRemove(avlTree, 4) // 次数 2 のノードを削除する
/* ノードを検索 */
val node = avlTree.search(7)
println("\n 見つかったノードオブジェクトは $node、ノードの値 = ${node?._val}")
}
@@ -0,0 +1,157 @@
/**
* File: binary_search_tree.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_tree
import utils.TreeNode
import utils.printTree
/* 二分探索木 */
class BinarySearchTree {
// 空の木を初期化する
private var root: TreeNode? = null
/* 二分木の根ノードを取得 */
fun getRoot(): TreeNode? {
return root
}
/* ノードを探索 */
fun search(num: Int): TreeNode? {
var cur = root
// ループで探索し、葉ノードを越えたら抜ける
while (cur != null) {
// 目標ノードは cur の右部分木にある
cur = if (cur._val < num)
cur.right
// 目標ノードは cur の左部分木にある
else if (cur._val > num)
cur.left
// 目標ノードが見つかったらループを抜ける
else
break
}
// 目標ノードを返す
return cur
}
/* ノードを挿入 */
fun insert(num: Int) {
// 木が空なら、根ノードを初期化する
if (root == null) {
root = TreeNode(num)
return
}
var cur = root
var pre: TreeNode? = null
// ループで探索し、葉ノードを越えたら抜ける
while (cur != null) {
// 重複ノードが見つかったら、直ちに返す
if (cur._val == num)
return
pre = cur
// 挿入位置は cur の右部分木にある
cur = if (cur._val < num)
cur.right
// 挿入位置は cur の左部分木にある
else
cur.left
}
// ノードを挿入
val node = TreeNode(num)
if (pre?._val!! < num)
pre.right = node
else
pre.left = node
}
/* ノードを削除 */
fun remove(num: Int) {
// 木が空なら、そのまま早期リターンする
if (root == null)
return
var cur = root
var pre: TreeNode? = null
// ループで探索し、葉ノードを越えたら抜ける
while (cur != null) {
// 削除対象のノードが見つかったら、ループを抜ける
if (cur._val == num)
break
pre = cur
// 削除対象ノードは cur の右部分木にある
cur = if (cur._val < num)
cur.right
// 削除対象ノードは cur の左部分木にある
else
cur.left
}
// 削除対象ノードがなければそのまま返す
if (cur == null)
return
// 子ノード数 = 0 or 1
if (cur.left == null || cur.right == null) {
// 子ノード数が 0 / 1 のとき、child = null / その子ノード
val child = if (cur.left != null)
cur.left
else
cur.right
// ノード cur を削除する
if (cur != root) {
if (pre!!.left == cur)
pre.left = child
else
pre.right = child
} else {
// 削除ノードが根ノードなら、根ノードを再設定
root = child
}
// 子ノード数 = 2
} else {
// 中順走査における cur の次ノードを取得
var tmp = cur.right
while (tmp!!.left != null) {
tmp = tmp.left
}
// ノード tmp を再帰的に削除
remove(tmp._val)
// tmp で cur を上書きする
cur._val = tmp._val
}
}
}
/* Driver Code */
fun main() {
/* 二分探索木を初期化 */
val bst = BinarySearchTree()
// 注意:挿入順序が異なると異なる二分木が生成される。このシーケンスからは完全二分木を生成できる
val nums = intArrayOf(8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15)
for (num in nums) {
bst.insert(num)
}
println("\n初期化した二分木は\n")
printTree(bst.getRoot())
/* ノードを探索 */
val node = bst.search(7)
println("見つかったノードオブジェクトは $node、ノードの値 = ${node?._val}")
/* ノードを挿入 */
bst.insert(16)
println("\nノード 16 を挿入後、二分木は\n")
printTree(bst.getRoot())
/* ノードを削除 */
bst.remove(1)
println("\nノード 1 を削除後、二分木は\n")
printTree(bst.getRoot())
bst.remove(2)
println("\nノード 2 を削除後、二分木は\n")
printTree(bst.getRoot())
bst.remove(4)
println("\nノード 4 を削除後、二分木は\n")
printTree(bst.getRoot())
}
@@ -0,0 +1,40 @@
/**
* File: binary_tree.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_tree
import utils.TreeNode
import utils.printTree
/* Driver Code */
fun main() {
/* 二分木を初期化 */
// ノードを初期化
val n1 = TreeNode(1)
val n2 = TreeNode(2)
val n3 = TreeNode(3)
val n4 = TreeNode(4)
val n5 = TreeNode(5)
// ノード間の参照(ポインタ)を構築する
n1.left = n2
n1.right = n3
n2.left = n4
n2.right = n5
println("\n二分木を初期化\n")
printTree(n1)
/* ノードの挿入と削除 */
val P = TreeNode(0)
// n1 -> n2 の間にノード P を挿入
n1.left = P
P.left = n2
println("\nノード P を挿入後\n")
printTree(n1)
// ノード P を削除
n1.left = n2
println("\nノード P を削除後\n")
printTree(n1)
}
@@ -0,0 +1,42 @@
/**
* File: binary_tree_bfs.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_tree
import utils.TreeNode
import utils.printTree
import java.util.*
/* レベル順走査 */
fun levelOrder(root: TreeNode?): MutableList<Int> {
// キューを初期化し、ルートノードを追加する
val queue = LinkedList<TreeNode?>()
queue.add(root)
// 走査順序を保存するためのリストを初期化する
val list = mutableListOf<Int>()
while (queue.isNotEmpty()) {
val node = queue.poll() // デキュー
list.add(node?._val!!) // ノードの値を保存する
if (node.left != null)
queue.offer(node.left) // 左子ノードをキューに追加
if (node.right != null)
queue.offer(node.right) // 右子ノードをキューに追加
}
return list
}
/* Driver Code */
fun main() {
/* 二分木を初期化 */
// ここではリストから直接二分木を生成する関数を利用する
val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7))
println("\n二分木を初期化\n")
printTree(root)
/* レベル順走査 */
val list = levelOrder(root)
println("\nレベル順走査のノード出力順 = $list")
}
@@ -0,0 +1,64 @@
/**
* File: binary_tree_dfs.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package chapter_tree
import utils.TreeNode
import utils.printTree
// 走査順序を格納するリストを初期化
var list = mutableListOf<Int>()
/* 先行順走査 */
fun preOrder(root: TreeNode?) {
if (root == null) return
// 訪問順序:根ノード -> 左部分木 -> 右部分木
list.add(root._val)
preOrder(root.left)
preOrder(root.right)
}
/* 中順走査 */
fun inOrder(root: TreeNode?) {
if (root == null) return
// 訪問優先順: 左部分木 -> 根ノード -> 右部分木
inOrder(root.left)
list.add(root._val)
inOrder(root.right)
}
/* 後順走査 */
fun postOrder(root: TreeNode?) {
if (root == null) return
// 訪問優先順: 左部分木 -> 右部分木 -> 根ノード
postOrder(root.left)
postOrder(root.right)
list.add(root._val)
}
/* Driver Code */
fun main() {
/* 二分木を初期化 */
// ここではリストから直接二分木を生成する関数を利用する
val root = TreeNode.listToTree(mutableListOf(1, 2, 3, 4, 5, 6, 7))
println("\n二分木を初期化\n")
printTree(root)
/* 先行順走査 */
list.clear()
preOrder(root)
println("\n先行順走査のノード出力順 = $list")
/* 中順走査 */
list.clear()
inOrder(root)
println("\n中間順走査のノード出力順 = $list")
/* 後順走査 */
list.clear()
postOrder(root)
println("\n後行順走査のノード出力順 = $list")
}
+25
View File
@@ -0,0 +1,25 @@
/**
* File: ListNode.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package utils
/* 連結リストノード */
class ListNode(var _val: Int) {
var next: ListNode? = null
companion object {
/* リストを連結リストにデシリアライズする */
fun arrToLinkedList(arr: IntArray): ListNode? {
val dum = ListNode(0)
var head = dum
for (_val in arr) {
head.next = ListNode(_val)
head = head.next!!
}
return dum.next
}
}
}
+107
View File
@@ -0,0 +1,107 @@
/**
* File: PrintUtil.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package utils
import java.util.*
class Trunk(var prev: Trunk?, var str: String)
/* 行列を出力する(Array) */
fun <T> printMatrix(matrix: Array<Array<T>>) {
println("[")
for (row in matrix) {
println(" $row,")
}
println("]")
}
/* 行列を出力する(List */
fun <T> printMatrix(matrix: MutableList<MutableList<T>>) {
println("[")
for (row in matrix) {
println(" $row,")
}
println("]")
}
/* 連結リストを出力 */
fun printLinkedList(h: ListNode?) {
var head = h
val list = mutableListOf<String>()
while (head != null) {
list.add(head._val.toString())
head = head.next
}
println(list.joinToString(separator = " -> "))
}
/* 二分木を出力 */
fun printTree(root: TreeNode?) {
printTree(root, null, false)
}
/**
* 二分木を出力
* This tree printer is borrowed from TECHIE DELIGHT
* https://www.techiedelight.com/c-program-print-binary-tree/
*/
fun printTree(root: TreeNode?, prev: Trunk?, isRight: Boolean) {
if (root == null) {
return
}
var prevStr = " "
val trunk = Trunk(prev, prevStr)
printTree(root.right, trunk, true)
if (prev == null) {
trunk.str = "———"
} else if (isRight) {
trunk.str = "/———"
prevStr = " |"
} else {
trunk.str = "\\———"
prev.str = prevStr
}
showTrunks(trunk)
println(" ${root._val}")
if (prev != null) {
prev.str = prevStr
}
trunk.str = " |"
printTree(root.left, trunk, false)
}
fun showTrunks(p: Trunk?) {
if (p == null) {
return
}
showTrunks(p.prev)
print(p.str)
}
/* ハッシュテーブルを出力 */
fun <K, V> printHashMap(map: Map<K, V>) {
for ((key, value) in map) {
println("${key.toString()} -> $value")
}
}
/* ヒープを出力 */
fun printHeap(queue: Queue<Int>?) {
val list = mutableListOf<Int?>()
queue?.let { list.addAll(it) }
print("ヒープの配列表現:")
println(list)
println("ヒープの木構造表現:")
val root = TreeNode.listToTree(list)
printTree(root)
}
+69
View File
@@ -0,0 +1,69 @@
/**
* File: TreeNode.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package utils
/* 二分木ノードクラス */
/* コンストラクタ */
class TreeNode(
var _val: Int // ノード値
) {
var height: Int = 0 // ノードの高さ
var left: TreeNode? = null // 左子ノードへの参照
var right: TreeNode? = null // 右子ノードへの参照
// シリアライズの符号化規則は以下を参照:
// https://www.hello-algo.com/chapter_tree/array_representation_of_tree/
// 二分木の配列表現:
// [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15]
// 二分木の連結リスト表現:
// /——— 15
// /——— 7
// /——— 3
// | \——— 6
// | \——— 12
// ——— 1
// \——— 2
// | /——— 9
// \——— 4
// \——— 8
/* リストを二分木にデシリアライズする: 再帰 */
companion object {
private fun listToTreeDFS(arr: MutableList<Int?>, i: Int): TreeNode? {
if (i < 0 || i >= arr.size || arr[i] == null) {
return null
}
val root = TreeNode(arr[i]!!)
root.left = listToTreeDFS(arr, 2 * i + 1)
root.right = listToTreeDFS(arr, 2 * i + 2)
return root
}
/* リストを二分木にデシリアライズする */
fun listToTree(arr: MutableList<Int?>): TreeNode? {
return listToTreeDFS(arr, 0)
}
/* 二分木をリストにシリアライズする: 再帰 */
private fun treeToListDFS(root: TreeNode?, i: Int, res: MutableList<Int?>) {
if (root == null) return
while (i >= res.size) {
res.add(null)
}
res[i] = root._val
treeToListDFS(root.left, 2 * i + 1, res)
treeToListDFS(root.right, 2 * i + 2, res)
}
/* 二分木をリストにシリアライズする */
fun treeToList(root: TreeNode?): MutableList<Int?> {
val res = mutableListOf<Int?>()
treeToListDFS(root, 0, res)
return res
}
}
}
+30
View File
@@ -0,0 +1,30 @@
/**
* File: Vertex.kt
* Created Time: 2024-01-25
* Author: curtishd (1023632660@qq.com)
*/
package utils
/* 頂点クラス */
class Vertex(val _val: Int) {
companion object {
/* 値リスト vals を入力し、頂点リスト vets を返す */
fun valsToVets(vals: IntArray): Array<Vertex?> {
val vets = arrayOfNulls<Vertex>(vals.size)
for (i in vals.indices) {
vets[i] = Vertex(vals[i])
}
return vets
}
/* 頂点リスト vets を入力し、値リスト vals を返す */
fun vetsToVals(vets: MutableList<Vertex?>): MutableList<Int> {
val vals = mutableListOf<Int>()
for (vet in vets) {
vals.add(vet!!._val)
}
return vals
}
}
}