1. はじめに
1. データ構造(FIFO / 先入れ先出し) 原則に
队列:
従った順序付けられた項目のセット。キューは新しい要素を末尾に追加し、先頭から要素を削除します。先进先出
最後に追加された要素はキューの最後になければなりません。(例如:去食堂排队打饭,排在前面的人先打到饭,先离开;排在后面的人后打到饭,后离开。)
栈:
(LIFO) 原則に従った順序先进后出
付きコレクション。新しく追加または削除された要素は、スタックの最上部と呼ばれるスタックの最後に格納され、もう一方の端はスタックの最下位と呼ばれます。スタックでは、新しい要素はスタックの上部近くにあり、古い要素はスタックの下部近くにあります。(例如:往口袋里面装东西,先装进去的放在最下面,后装进去的放在最上面,取出的时候只能从上往下取。)
链表:
順序付けられた要素のコレクションを格納しますが、配列とは異なり、リンク リスト内の要素はメモリ内に連続して配置されません。各要素は、要素自体を格納するノードと、次の要素への参照 (ポインタ/リンク) で構成されます。
集合:
順序付けされていない一意の (つまり、繰り返しのない) アイテムのセットで構成されます。このデータ構造は、有限セットと同じ数学的概念を使用しますが、コンピューター サイエンスのデータ構造に適用されます。
字典:
[キー、値] ペアの形式のデータ構造。Javascript のオブジェクトと同様に、キー名を使用して特定の要素をクエリします。
哈希表:
キー値(Key value)に従って直接アクセスされるデータ構造です。キー値をテーブル内の場所にマッピングしてレコードにアクセスし、検索を高速化します。このマッピング関数をハッシュ関数と呼び、レコードを格納する配列をハッシュテーブルと呼びます。
テーブル M が与えられたとき、関数 f(key) があり、任意のキー値 key に対して、その関数を代入してそのキーを含むテーブル内のレコードのアドレスが取得できる場合、テーブル M はハッシュと呼ばれます (ハッシュ). table の関数 f(key) はハッシュ (Hash) 関数です。
树:
階層関係を持つ n (n>=1) 個の有限ノードの集合。逆さまの木のように見えるため、つまり、根が上を向き、葉が下を向いているため、「ツリー」と呼ばれます。はい、基本的には 1 対多の関係であり、ツリーはグラフの特殊な形式とみなすこともできます。
图:
グラフはネットワーク構造の抽象モデルです。グラフはエッジで接続されたノード (頂点) のグループです。道路グラフ、関係グラフ、多対多など、あらゆる二項関係をグラフで表すことができます。関係。
2. アルゴリズム
並べ替えアルゴリズム:
冒泡排序(升序)
: 隣接する 2 つの要素を 1 つずつ比較し、前の要素が後ろの要素より大きい場合、2 つの順序が入れ替わります。あたかもバブルが上昇するかのように、要素項目が正しい順序に移動します。表面なので名前が付けられています。(バブルソートの各ラウンドで、少なくとも 1 つの要素が正しい位置に表示されます)
快速排序(升序)
: ベンチマーク値を選択し、各要素をベンチマーク値と比較します。基準値より小さい要素は基準値の左側に配置され、基準値より大きい要素は基準値の右側に配置され、左側と右側で再帰的に演算が行われます。通常、中央の要素が基準値として選択されます。
选择排序:
毎回ソートするデータ要素から最小 (または最大) の要素を選択し、それをシーケンスの開始位置に格納し、ソートが完了するまで繰り返します。
插入排序:
ソート済みの順序付きデータにデータを挿入し、番号を1つ増やした新しい順序付きデータを得るアルゴリズムで、少量のデータをソートする場合に適しています。
归并排序:
それぞれの小さなシーケンスが分割できなくなるまで、元のシーケンスを小さなシーケンスに分割し、マージを実行します。つまり、小さなシーケンスを大きなシーケンスにマージし、ソートされた大きなシーケンスが 1 つだけになるまで、マージ プロセスを比較およびソートします。最後のシーケンスの場合、時間計算量は O(n log n) です。
さまざまな時間計算量の直感的な比較:
検索アルゴリズム:
顺序搜索
: 指定された要素と同じ要素が見つかるまで、ターゲット要素とリスト内の各要素を 1 つずつ比較しますが、欠点は効率が低いことです。
二分搜索
: 順序付きリストでは、中央の値に基づいて 2 つのサブリストに分割し、ターゲット要素と中央の値を比較して、ターゲット要素が見つかるまでターゲットのサブリストでこのメソッドを再帰します。
その他のアルゴリズム:
贪心算法
: 問題を解決する際に、全体の状況を考慮せず、常に局所的な最適解を作成します。
动态规划
: 問題を解く際、上記で得られた局所最適解から大域最適解が導出されます。
复杂度概念
: 実行のライフサイクル全体を通じてメソッドが占有する必要があるリソースには、主に時間リソースと空間リソースが含まれます。
2. データ構造
キュー
概念
: 先进先出
(FIFO / 先入れ先出し) 原則に従った順序付けされたアイテムのセット。キューは末尾に新しい要素を追加し、先頭から要素を削除します。最後に追加された要素はキューの最後になければなりません。
Javascriptでキュークラスを実装します。
1. クラスを作成します。
class Queue{
constructor(items) {
this.items = items || []
}
// 1. 在末尾添加元素
enqueue(element){
this.items.push(element)
}
// 2. 在头部删除元素
dequeue(){
return this.items.shift()
}
// 3. 获取头部元素
front(){
return this.items[0]
}
// 4. 获取队列长度
get size(){
return this.items.length
}
// 5. 判断是否是空队列
get isEmpty(){
return !this.items.length
}
// 6. 清空队列
clear(){
this.items = []
}
}
2. クラスを使用します。
const queue = new Queue() // 类的实例化
queue.isEmpty // true
queue.enqueue('John') // {items: ['John']}
queue.enqueue('Jack') // {items: ['John','Jack']}
queue.enqueue('Camila') // {items: ['John','Jack','Camila']}
queue.size // 3
queue.isEmpty // false
queue.dequeue() // John
queue.dequeue() // Jack
queue.dequeue() // Camila
優先列
概念
:元素的添加和移除是基于优先级的,不在满足完全意义的先进先出。
たとえば、空港での搭乗順序では、ファーストクラスとビジネスクラスの乗客がエコノミークラスの乗客より優先され、これらの乗客は飛行機に搭乗するために通常の列を通る必要はありません。または、病院に行って医師の診察を受けてください。医師は症状の重症度に応じて、重篤な症状を優先します。
Javascript でキュー クラスを実装するには:
1. クラスを作成します。
class PriorityQueue {
constructor() {
this.items = []
}
enqueue(element, priority){
const queueElement = {
element, priority }
if (this.isEmpty) {
this.items.push(queueElement)
} else {
// 在列表中找到第一个比后进入的元素的priority大的元素的位置,如果有,将这个元素插入这里。如果没有,将这个元素放在最后面。
const preIndex = this.items.findIndex((item) => queueElement.priority < item.priority)
if (preIndex > -1) {
this.items.splice(preIndex, 0, queueElement)
} else {
this.items.push(queueElement)
}
}
}
dequeue(){
return this.items.shift()
}
front(){
return this.items[0]
}
clear(){
this.items = []
}
get size(){
return this.items.length
}
get isEmpty(){
return !this.items.length
}
print() {
console.log(this.items)
}
}
2. クラスを使用します。
const priorityQueue = new PriorityQueue()
priorityQueue.enqueue('Wangjiajia', 2) // {items: [{element: 'Wangjiajia', priority: 2}]}
priorityQueue.enqueue('Wangtongtong', 1) // {items: [{element: 'Wangtongtong', priority: 1},{element: 'Wangjiajia', priority: 2}]}
priorityQueue.enqueue('Davide', 4) // {items: [{element: 'Wangtongtong', priority: 1},{element: 'Wangjiajia', priority: 2},{element: 'Davide', priority: 4}]}
priorityQueue.enqueue('Tom', 3) // {items: [{element: 'Wangtongtong', priority: 1},{element: 'Wangjiajia', priority: 2},{element: 'Tom', priority: 3},{element: 'Davide', priority: 4}]}
priorityQueue.enqueue('James', 2) // {items: [{element: 'Wangtongtong', priority: 1},{element: 'Wangjiajia', priority: 2},{element: 'James', priority: 2},{element: 'Tom', priority: 3},{element: 'Davide', priority: 4}]}
ベクトル空間を最大限に活用し、「偽のオーバーフロー」現象を克服するために、循環キューは
概念:
ベクトル空間を端と端で接続されたリングとして想像し、このベクトルを循環ベクトルと呼びます。その中に格納されるキューを循環キュー(Circular Queue)と呼びます。わかりやすく言うと、循環キューとは連続するキューを端から端まで接続するもので、キューの要素を格納するテーブルを論理的にリングとみなして循環キューとなります。
假溢出:
キューのスペースは使い果たされませんが、要素のオーバーフローが発生します。
初めて実装されたキュー クラスに基づいた、循環参照の単純な実装例:
class LoopQueue extends Queue {
constructor(items) {
super(items)
}
getIndex(index) {
const length = this.items.length
return index > length ? (index % length) : index
}
find(index) {
return !this.isEmpty ? this.items[this.getIndex(index)] : null
}
}
使用:
const loopQueue = new LoopQueue(['Surmon'])
loopQueue.enqueue('SkyRover')
loopQueue.enqueue('Even')
loopQueue.enqueue('Alice')
console.log(loopQueue.size, loopQueue.isEmpty) // 4 false
(loopQueue.find(26) // 'Evan'
(loopQueue.find(87651) // 'Alice'
スタック:
概念
: 先进后出
(LIFO) 原則に従う順序付きコレクション。新しく追加された要素または削除される要素は、スタックの最上部と呼ばれるスタックの最後に格納され、もう一方の端はスタックの最下位となります。そして出るときはスタックの上から出ます。スタックでは、新しい要素はスタックの上部近くにあり、古い要素はスタックの下部近くにあります。`
JavaScript を使用して、配列メソッドに基づいたスタックの機能を実現します。
class Stack {
constructor() {
this.items = []
}
// 入栈
push(element) {
this.items.push(element)
}
// 出栈
pop() {
return this.items.pop()
}
// 末位
get peek() {
return this.items[this.items.length - 1]
}
// 是否为空栈
get isEmpty() {
return !this.items.length
}
// 尺寸
get size() {
return this.items.length
}
// 清空栈
clear() {
this.items = []
}
// 打印栈数据
print() {
console.log(this.items.toString())
}
}
スタックを使用します。
// 实例化一个栈
const stack = new Stack()
console.log(stack.isEmpty) // true
// 添加元素
stack.push(5) // [5]
stack.push(8) // [5,8]
// 读取属性再添加
console.log(stack.peek) // 8
stack.push(11) // [5,8,11]
stack.pop() // 11
console.log(stack.size) // 3
console.log(stack.isEmpty) // false
リンク リスト:
概念:
順序付けられた要素のコレクションを格納しますが、不同于数组,链表中的元素在内存中并不是连续放置的;
各要素は 1 つずつ格納されます元素本身的节点和一个指向下一个元素的引用(指针/链接)组成
。
分类:
単一リンク リスト、二重リンク リスト、循環リンク リスト
链表和数组的区别
:
* 配列では要素を追加または削除するときに他の要素を移動する必要がありますが、リンク リストではその必要がありません。リンク リストではポインターを使用する必要があるため、使用する場合は特に注意する必要があります。
* 配列はその要素のいずれかにアクセスでき、リンクされたリストは目的の要素が見つかるまで最初から繰り返す必要があります。
リンク リストの一般的な方法:
1、append(element):
リストの最後に新しい項目を
2、insert(position, element):
追加する リスト内の特定の位置に新しい項目を挿入します。
3、remove(element):
リストから項目を削除します。
4、removeAt(position):
リスト内の特定の位置から項目を削除します。
5、indexOf(element):
リスト内の要素のインデックスを返します。要素がリストに存在しない場合は -1 を返します。
6、getElementAt(index):
リンク リスト内の特定の位置にある要素を返します。そのような要素が存在しない場合は、未定義を返します。
7、isEmpty():
リンク リストに要素が含まれていない場合は、true を返します。リンク リストの長さが 0 より大きい場合は、false を返します。 。
8、size():
リンクされたリストに含まれる要素の数を返します。配列の長さプロパティに似ています。
9、toString():
リスト項目は Node クラスを使用するため、JavaScript オブジェクトから継承したデフォルトの toString メソッドをオーバーライドして、要素の value 要素のみを出力するようにする必要があります。
整体操作方法和数组非常类似,
因为链表本身就是一种可以代替数组的结构.
使用javascript描述一个单向链表
:
一方向連結リスト:
生活の例: 電車を連結リストとみなすことができ、各セクションは車両と車両間の連結ベルトで構成され、この連結ベルトはポインタとみなすことができます。
JavaScript を使用して一方向リンク リストを記述します。
// 链表节点
class Node {
constructor(element) {
this.element = element
this.next = null
}
}
// 单向链表
class LinkedList {
constructor() {
this.head = null
this.length = 0
}
// 1. 追加元素
// 向链表尾部追加数据可能有两种情况:
// 链表本身为空, 新添加的数据是唯一的节点.
// 链表不为空, 需要向其他节点后面追加节点.
append(element) {
const node = new Node(element)
let current = null
// 链表本身为空, 新添加的数据是唯一的节点.
if (this.head === null) {
this.head = node
} else {
// 链表不为空, 需要向其他节点后面追加节点.
current = this.head
while(current.next) {
current = current.next
}
current.next = node
}
this.length++
}
// 2. 任意位置插入元素
insert(position, element) {
// 1.检测越界问题: 越界插入失败, 返回false
if (position < 0 || position > this.length) return false
// 2.找到正确的位置, 并且插入数据
// 定义要插入的变量newNode, current当前节点, previous上一个节点
let newNode = new Node(element)
let current = this.head // 初始值为head, 对第一个元素的引用
let previous = null // 存储当前current的上一个节点
let index = 0 // 位置
// 3.判断是否列表是否在第一个位置插入
if (position == 0) {
newNode.next = current
this.head = newNode
} else {
while (index++ < position) {
// 向前赶, 直到找到当前位置position
previous = current
current = current.next
}
// index === position, 找到要插入的位置
newNode.next = current
previous.next = newNode
}
// 4.length+1
this.length++
}
// 3. 从链表中任意移除一项
removeAny(position) {
//边界检查,越界返回false
if(position < 0 || position > this.length-1){
return false
}
let current = this.head
let previous = null
let index = 0
// 如果删除第一个元素
if(position == 0){
this.head = current.next
}else{
// 不是删除第一个找到删除的位置
while(index++ < position){
previous = current
current = current.next
}
previous.next = current.next
}
this.length--
return current.element
}
// 4. 寻找元素下标
findIndex(element) {
let current = this.head
let index = 0
//遍历链表直到找到data匹配的position
while (current) {
if (element === current.element) {
return index
}
current = current.next
index++
}
return false
}
// 5. 从链表的特定位置移除一项。
remove(element) {
const index = this.indexOf(element)
return this.removeAt(index)
}
// 6. 判断是否为空链表
isEmpty() {
return !this.length
}
// 7. 获取链表长度
size() {
return this.length
}
// 8. 转为字符串
toString() {
let current = this.head
let str = ''
while (current) {
str += ` ${
current.element}`
current = current.next
}
return str
}
}
リンクリストクラスの使用:
const linkedList = new LinkedList()
console.log(linkedList) // LinkedList {head: null, length: 0}
// 1. 在末尾追加元素
linkedList.append(2) // LinkedList {head: {element:2,next:null}, length: 1}
linkedList.append(4) // LinkedList {head: {element:2,next:{element:4,next:null}}, length: 2}
linkedList.append(6) // LinkedList {head: {element:2,next:{element:4,next:{element:6,next:null}}}, length: 3}
// 2. 在任意位置插入一个元素
linkedList.insert(2, 18) // LinkedList {head: {element:2,next:{element:4,next:{element:18,next:{element:6,next:null}}}}, length: 4}
// 3. 从链表中任意位置删除一项
linkedList.removeAny(2) // 18
// 4. 寻找元素下标
linkedList.findIndex(6) // 2
linkedList.findIndex(18) // false
linkedList.findIndex(20) // false
// 5. 从链表的特定位置移除一项。
linkedList.remove(4)
// 6. 判断是否为空链表
linkedList.isEmpty() // false
// 7. 获取链表长度
linkedList.size() // 2
// 8. 转为字符串
linkedList.toString() // ' 2 4 18 6'
二重リンク リスト:
概念:
二重リンク リストと通常のリンク リストの違いは、リンク リストではノードが次のノードへのリンクのみを持つのに対し、二重リンク リストではリンクが双方向であることです。つまり、1 つは次のノードにリンクします。次の要素、および他のリンクは前方の要素です。下の図に示すように、
javacsript を使用して二重リンク リスト クラスを実装します。
class Node{
constructor(element){
this.element = element
this.next = null
this.prev = null
}
}
// 双向链表
class DoublyLinkedList {
constructor() {
this.head = null
this.tail = null
this.length = 0
}
// 1. 任意位置插入
insert(position,element){
// 1.1 检测越界问题: 越界插入失败, 返回false
if (position < 0 || position > this.length) return false
let newNode = new Node(element)
let current = this.head // 初始值为head, 对第一个元素的引用
let previous = null // 存储当前current的上一个节点
let index = 0 // 位置
// 1.2 如果插在了首位
if(position == 0){
// 空链表
if(!head){
this.head = node
this.tail = node
}else{
this.head = node
node.next = current
current.prev = node
}
}
// 1.3 如果插在了末位
else if(position == this.length) {
this.tail = node
current.next = node
node.prev = current
}else {
// 如果插在了中间位置
// 找到插入的位置
while(idex++ < position){
previous = current
current = current.next
}
// 插入进去
node.next = current
previous.next = node
node.pre = previous
current.pre = node
this.length++
return true
}
}
// 2. 移除任意位置元素
removeAny(position){
// 2.1 边界检查,越界返回false
if(position < 0 || position > this.length-1) return false
let current = this.head
let previous = null
let index = 0
// 2.2 如果删除的是首位
if(position == 0){
this.head = current.next
this.head.prev = null
}else if(position == this.length - 1){
// 2.3 如果删除的是末位
this.tail = current.pre
this.tail.next = null
}else {
// 2.4 中间项
// 找到删除的位置
while(index++ < position){
previous = current
current = current.next
}
previous.next = current.next
current.prev = current.next.prev
this.length--
return current.element
}
}
}
循環リンク リスト:
概念:
循環リンク リストは、単一リンク リストのように一方向参照のみを持つことも、二重リンク リストのように双方向参照を持つこともできます。
单向循环链表
: 最後の要素から次の要素へのポインタ (tail.next) は null への参照ではなく、次の図に示すように最初の要素 (head) を指します。 双方向循環リンク リスト: ポインタ
テール最後の要素から次の要素へ next は null ではありませんが、最初の要素 (head) を指し、最初の要素のポインタ head.prev は前の要素を指しますが、null ではなく最後の要素の tail を指します。図に示すように:
リンク リストの利点: リンク リスト内の要素を移動せずに、要素を簡単に追加および削除できます。したがって、多くの要素を追加および削除する必要がある場合は、配列ではなくリンク リストを選択するのが最適です。
コレクション:
概念
: コレクションは、順序付けされていない一意の (重複しない) アイテムのセットです。現在、Set の実装は ES6 に組み込まれています。
集合中常用的一些方法
:
add() 要素を追加
delete() 要素を削除し、ブール値を返します。削除が成功した場合は true を返し、削除が失敗した場合は false を返します。 has()
要素が含まれているかどうかを判断し、ブール値を返します。
clear() 設定されたデータ構造の
サイズをクリアします。括弧、return この構造体の長さ
ES6 でのコレクションの使用:
const arr = [1,2,2,3,3,3,4,4,5]
const str = 'wangjiajiawwwjiajiawwwww'
// 1.添加元素
new Set(arr) // Set{1,2,3,4,5}
new Set(str ) // Set{'w', 'a', 'n', 'g', 'j', 'i'}
new Set(arr).add(8) // Set{1,2,3,4,5,8}
// 2.删除元素
new Set(arr).delete(2) // true
new Set(arr).delete(9) // false
// 3.判断是否含有某个元素
new Set(arr).has(2) // true
new Set(arr).has(9) // false
// 4.清空set数据结构
new Set(arr).clear() // undefined
// 5.没有括号,返回此结构长度
new Set(arr).size // 5
JavaScript でのコレクションの使用:
// hasOwnProperty(propertyName)方法 是用来检测属性是否为对象的自有属性,如果是,返回true,否则返回false; 参数propertyName指要检测的属性名;
// hasOwnProperty() 方法是 Object 的原型方法(也称实例方法),它定义在 Object.prototype 对象之上,所有 Object 的实例对象都会继承 hasOwnProperty() 方法。
class Set {
constructor() {
this.items = {
}
}
has(value) {
return this.items.hasOwnProperty(value)
}
add(value) {
if (!this.has(value)) {
this.items[value] = value
return true
}
return false
}
remove(value) {
if (this.has(value)) {
delete this.items[value]
return true
}
return false
}
get size() {
return Object.keys(this.items).length
}
get values() {
return Object.keys(this.items)
}
}
const set = new Set()
set.add(1)
console.log(set.values) // ["1"]
console.log(set.has(1)) // true
console.log(set.size) // 1
set.add(2)
console.log(set.values) // ["1", "2"]
console.log(set.has(2)) // true
console.log(set.size) // 2
set.remove(1)
console.log(set.values) // ["2"]
console.log(set.has(1)) // false
set.remove(2)
console.log(set.values) // []
コレクションに対して次の操作を実行できます。 :
并集
2 つのコレクションがある場合、両方のコレクション内のすべての要素を含む新しいコレクションを返します。
交集
: 2 つのコレクションを指定すると、両方のコレクションに共通の要素を含む新しいコレクションを返します。
差集
: 2 つのコレクションがある場合、最初のコレクションには存在し、2 番目のコレクションには存在しないすべての要素を含む新しいコレクションを返します。
Union:
Union の数学的概念: A∪B として示される集合 A と B の和集合。次のように定義されます: A∪B = { x | x∈AV x∈B }、つまり x (要素) が A に存在することを意味します。にある、または B に x が存在します。図に示すように:
Set クラスに基づいて Union メソッドを実装します。
union(otherSet) {
const unionSet = new Set()
// 先添加其中一个集合的元素放在unionSet中
this.values.forEach((v, i) => unionSet.add(this.values[i]))
// 在把另一个集合的元素放入unionSet中
otherSet.values.forEach((v, i) => unionSet.add(otherSet.values[i]))
return unionSet
}
交差: 和
集合の数学的概念: セット A と B の交差。A∩B として表され、次のように定義されます: A∩B = { x | x∈A ∧ x∈B }、つまり x (要素) が存在することを意味します。 A 、そして x は B に存在します。図に示すように:
Set クラスに基づいて交差メソッドを実装します。
intersection(otherSet) {
const intersectionSet = new Set()
// 从集合A开始循环判断,如果这个元素也在集合B中,那就说明这个元素是集合A,B公有的。这时候把这个元素放到一个新的集合中
this.values.forEach((v, i) => {
if (otherSet.has(v)) {
intersectionSet.add(v)
}
})
return intersectionSet
}
差分集合:
差分集合の数学的概念: 集合 A と B の差分集合。AB で表され、次のように定義されます: AB = { x | x∈A ∧ x∉B }、A に x (要素) が存在することを意味します。 、Bには存在しません。図に示すように、
先ほどの Set クラスに基づいて差分セット AB を実装する方法:
difference(otherSet) {
// 从集合A开始循环判断,如果这个元素不在集合B中。说明这个元素是A私有的,此时把这个元素放入一个新的集合中。
const differenceSet = new Set()
this.values.forEach((v, i) => {
if (!otherSet.has(v)) {
differenceSet.add(v)
}
})
return differenceSet
}
サブセット:
サブセットの数学的概念: 図に示すように、セット A は B のサブセット、またはセット B にはセット A が含まれます。
今の Set クラスに基づいてサブセット メソッドを実装します。
// 在这里this代表集合A,otherSet代表集合B
subset(otherSet){
if(this.size > otherSet.size){
return false
}else{
// 只要A里面有一个元素不在B里面就说明A不是B的子集,后面元素不用在判断了
this.values.every(v => !otherSet.has(v))
}
}
ディクショナリ:
セット、ディクショナリ、およびハッシュ テーブルはすべて、一意のデータを格納できます。ディクショナリは、JavaScript の Object オブジェクトと同様に、キーと値のペアの形式でデータを格納します。
JavaScript での辞書の実装:
class Dictionary {
constructor() {
this.items = {
}
}
set(key, value) {
this.items[key] = value
}
get(key) {
return this.items[key]
}
remove(key) {
delete this.items[key]
}
get keys() {
return Object.keys(this.items)
}
get values() {
/*
也可以使用ES7中的values方法
return Object.values(this.items)
*/
// 在这里我们通过循环生成一个数组并输出
return Object.keys(this.items).reduce((r, c, i) => {
r.push(this.items[c])
return r
}, [])
}
}
辞書を使う:
const dictionary = new Dictionary()
dictionary.set('Wangjiajia', '[email protected]')
dictionary.set('Wangtongtong', '[email protected]')
dictionary.set('davide', '[email protected]')
console.log(dictionary) // {items:{'Wangjiajia', '[email protected]','Wangtongtong', '[email protected]','davide', '[email protected]'}}
console.log(dictionary.keys) // ['Wangjiajia','Wangtongtong','davide']
console.log(dictionary.values) // [[email protected]','[email protected]','[email protected]']
console.log(dictionary.items) // {'Wangjiajia': '[email protected]','Wangtongtong': '[email protected]','davide':'[email protected]'}
ハッシュ表:
根据关键码值(Key value)而直接进行访问的数据结构。
キー値をテーブル内の場所にマッピングしてレコードにアクセスし、検索を高速化します。このマッピング関数をハッシュ関数と呼び、レコードを格納する配列をハッシュテーブルと呼びます。给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
ハッシュテーブルの模式図:
哈希表冲突:
ハッシュ関数でハッシュ値を取得する場合、その値の添え字には既にデータが格納されている可能性が高く、この時点で競合が発生します。
解决冲突:
チェーンアドレス方式、オープンアドレス方式。
JavaScript でハッシュテーブルを実装します。
function HashTable(){
// 初始化哈希列表
// 1. 设置哈希表数组
this.item = []
// 2. 设置哈希表长度
this.limit = 10
// 3. 设置表中数据量
this.total = 0
// 设置哈希函数
HashTable.prototype.hashFunc = function(key, limit){
let keyvalue = 0
for(let i=0; i<key.length; i++){
keyvalue=keyvalue * 12 + key.charCodeAt(i)
}
//使用取余运算计算hashCode
return keyvalue%limit
}
// 增、改
HashTable.prototype.put = function(key, value){
// 根据hashCode找到对应下标修改或者添加数据
let index = this.hashFunc(key, this.limit)
let arr = this.item[index]
// 如果这个下标所处的位置已经存放过数据,即arr有值,此时存在哈希表冲突
if(arr){
let completed = false
// 遍历数组,如果发现重名数据则直接修改
arr.forEach(item=>{
if(key===item[0]){
completed = true
return item[1] = value
}
})
// 如果没有重名则在末尾添加新数据
if(!completed){
arr.push([key, value])
this.total++
}
}else{
//如果basket为null则重新创建数组
this.item[index] = [[key, value]]
this.total++
}
}
// 查
HashTable.prototype.get = function(key){
let index = this.hashFunc(key, this.limit)
let basket = this.item[index]
//如果basket为null则没有对应数据
if(basket===undefined){
return null
}else{
//如果有basket, 则遍历basket,遍历完没有对应key则不存在对应数据
for(let i = 0; i < basket.length; i++){
if(key===basket[i][0]) return basket[i][1]
}
return null
}
}
// 删
HashTable.prototype.remove = function(key){
let index = this.hashFunc(key, this.limit)
let basket = this.item[index]
//如果basket为null则没有对应数据
if(!basket){
return null
}else{
//如果有basket, 则遍历basket,遍历完没有对应key则不存在对应数据
for(let i = 0; i < basket.length; i++){
if(key===basket[i][0]){
this.total--
return basket.splice(i, 1)
}
}
return null
}
}
//求表长
HashTable.prototype.size = function(){
return this.total
}
//判断表空
HashTable.prototype.isEmpty = function(){
return this.total===0
}
}
木:
-
概念:是一种天然具有递归属性的非顺序数据结构,是一种特殊的图(无向无环)。
n個のノードで構成されており、一定の階層関係を持っています。 -
根结点:树的最顶端的节点,根节点只有一个
-
子节点:除根节点之外,并且本身下面还连接有节点的节点。
-
叶子结点:自己下面不再连接有节点的节点(即末端),称为叶子节点(又称为终端结点),度为0
-
深さ: ノードからルート ノードまでのノードの数
-
高さ: すべてのノードの深さの最大値
-
普通の木:
-
二分木:
- ツリーの各ノードは最大 2 つの子ノードを持つことができます
- バイナリ ツリーは空、つまりノードがない場合もあり、空でない場合は、左右のサブツリーを持つ 2 つの互いに素なバイナリ ツリーで構成されます。
-
二分探索木:
- 空ではない左側のサブツリーのキー値がルート ノードのキー値より小さい
- 空ではない右サブツリーのキー値がルート ノードのキー値より大きい
- 左右のサブツリーは両方とも二分探索ツリーです
-
完全なバイナリ ツリーと完全なバイナリ ツリー 完全な
バイナリ ツリーとは、各ノードに 2 つの子ノードがあり、左右のサブツリーが対称であることを意味します。完全な二分木には、完全に対称ではない枝があります。
-
赤黒バイナリ ツリーは、
红黑二叉树本质上就是一种二叉搜索树,
挿入や削除などの操作中に特定の操作を通じてバイナリ ツリーのバランスを維持します。红黑二叉树本身是复杂的
ただし、最悪の場合の実行時間も非常に良好で、実際には効率的です。検索、挿入、削除を O(log n) 時間で実行できます (n はツリー内の要素の数です)。
赤黒二分木は、二分探索木に基づいて次の要件を満たす必要があります。- ノードは赤または黒である必要があります
- ルートノードは黒である必要があります
- ノードが赤の場合、その子ノードは黒でなければなりません (必ずしもその逆ではありません)
- ルート ノードからリーフ ノードまで 2 つの連続した赤いノードがあってはなりません
- 任意のノードから各リーフ ノードまで、すべてのルート上の黒いノードの数は同じです
JavaScript を使用してバイナリ ツリーのいくつかの操作を実装します。
// 封装二叉树节点
class Node {
constructor(data){
this.data = data
this.left = null
this.right = null
}
}
// 封装二叉搜索树
class BinarySearchTree {
constructor(){
this.root = null // 定义二叉树根节点
}
// 插入节点
insert(key){
const newNode = new Node(key)
// 定义插入节点的函数,比根节点小的放左边,比根节点大的放右边
function insertNode(node, newNode){
if(newNode.key < node.key){
// 判断根节点的左侧是否有节点,如果没有节点则直接插入,如果有节点则递归执行insertNode函数
if(node.left == null){
node.left = newNode
}else{
insertNode(node.left, newNode)
}
}else{
// 判断根节点的右侧是否有节点,如果没有节点则直接插入,如果有节点则递归执行insertNode函数
if(node.right == null){
node,right = newNode
}else{
insertNode(node.right, newNode)
}
}
}
// 如果是空二叉树,则插入的节点就是根节点,否则正常插入
if(!this.root){
this.root = newNode
}else{
insertNode(this.root, newNode)
}
}
}
二分探索ツリー クラスの使用:
const tree = new BinarySearchTree()
tree.insert(11)
tree.insert(7)
tree.insert(5)
tree.insert(3)
tree.insert(9)
tree.insert(8)
tree.insert(10)
tree.insert(15)
tree.insert(13)
tree.insert(12)
tree.insert(14)
tree.insert(20)
tree.insert(18)
tree.insert(25)
バイナリ ツリー トラバーサル: ツリーの各ノードにアクセスし、それらに対して特定の操作を実行するプロセス
前序遍历:
最初にルート ノードにアクセスし、次に左側のノードにアクセスし、最後に右側のノードにアクセスします (左側と右側のサブツリーに対して同じ操作が実行されます)根左右
。中序遍历
: 最初に左側のノードにアクセスし、次にルート ノードにアクセスし、最後に右側のノードにアクセスします。(左根右)
后序遍历
: 最初に左側のノードにアクセスし、次に右側のノードにアクセスし、最後にルート ノードにアクセスします。(左右根)
事前注文トラバーサル:
function preorder(root){
// 排除根节点为空的情况
if(!root) return
// 1. 访问根节点
console.log(root.key) // 11 7 5 3 6 9 8 10 15 13 12 14 20 18 25
// 2. 递归对根节点的左子树进行先序遍历
preorder(root.left)
// 3. 递归对根节点的右子树进行先序遍历
preorder(root.right)
}
preorder(tree)
インオーダートラバーサル:
const inorder = (root) => {
if(!root) return
// 1. 递归对根节点的左子树进行中序遍历
inorder(root.left)
// 2. 访问根节点
console.log(root.key) // 3 5 6 7 8 9 10 11 12 13 14 15 18 20 25
// 3. 递归对根节点的右子树进行中序遍历
inorder(root.right)
}
inorder(tree)
事後トラバーサル:
const postorder = (root) => {
if (!root) return
// 1. 递归对根节点的左子树进行后序遍历
postorder(root.left)
// 2. 递归对根节点的左子树进行后序遍历
postorder(root.right)
// 3. 访问根节点
console.log(root.key) // 3 6 5 8 10 9 7 12 14 13 18 25 20 15 11
}
postorder(tree)
最小値と最大値を検索します。
最小値が左端、最大値が右端になります。
function minNum(node) {
const minNode = node => {
return node ? (node.left ? minNode(node.left) : node.key) : null
}
return minNode(node || this.root)
}
function maxNum(node) {
const maxNode = node => {
return node ? (node.right ? maxNode(node.right) : node.key) : null
}
return maxNode(node || this.root)
}
minNum(tree.root) // 3
maxNum(tree.root) // 25
特定の値を検索します。
function search(node,key){
const searchNode = (node,key)=>{
if(node === null)return false
if(node.key === key) return true
return searchNode( key < node.key ? node.left : node.right, key)
}
return searchNode(node, key)
}
search(tree.root,3) // true
search(tree.root,3) // false
写真:
- 概念: 一連の頂点とそれらを接続するエッジで構成されるデータ構造
- 表現方法: G=(V, E) V はグラフ G の頂点の集合、E はグラフ G の辺の集合です。
無向グラフ: 頂点を接続するエッジには方向がありません
说明
。エッジによって接続された頂点は、A と B が隣接する、A と D が隣接する、A と C が隣接する、A と E が隣接しないなど、隣接頂点と呼ばれます。頂点の次数は、その隣接頂点の数です。たとえば、A には次数 3 があり、E には次数 2 があります。グラフにサイクルがある場合、そのグラフは循環グラフと呼ばれ、2 つの頂点ごとにパスがある場合、グラフは接続されていると言われます。
有向グラフ: 頂点を接続するエッジには方向がある
说明:
2 つの頂点間に両方向のパスがある場合、グラフは強く接続されています。たとえば、C と D は強く結びついていますが、A と B は結びついていません。
重み付きグラフ: グラフの端に重みが与えられます。
グラフの表現:
- 隣接行列
- 隣接リスト
隣接行列:
1. グラフの構造を表すために 2 次元配列を使用できます
2. グラフが疎グラフの場合、この 2 次元配列内の多くの領域には値 0 が割り当てられ、無駄になりますコンピュータのストレージスペース
3. 隣接マトリクスの概略図
说明:
2 次元配列で表され、2 つの頂点が隣接している場合、この値は 1 として記録され、そうでない場合は 0 として記録されます。この方法では、多数の 0 が記録され、コンピュータのパフォーマンスが無駄に消費される可能性があります。この頂点に隣接する頂点が 1 つしかない場合でも、行全体を反復していることになるためです。例: 上の I 頂点。
隣接リスト: 頂点と、この頂点に隣接する他の頂点のリストで構成され、メモリ消費を削減できます。
JavaScript を使用してグラフを作成します。
class Graph {
constructor(){
this.vertices = [] // 用来保存顶点的数组
this.adjList = {
} // 用来保存边的字典,可以直接用一个对象代替。
}
// 添加顶点
addVertex(v){
this.vertices.push(v)
this.adjList[v] = []
}
// 添加边
addEdge(v,w){
this.adjList[v].push(w)
this.adjList[w].push(v)
}
// 返回字符串
graphToString(){
return this.vertices.reduce((r,v) => {
return this.adjList[v].reduce((r,w) => {
return r + `${
w}`
},`${
r}\n${
v} => `)
},'')
}
}
const graph = new Graph();
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'].forEach(c => graph.addVertex(c))
graph.addEdge('A', 'B')
graph.addEdge('A', 'C')
graph.addEdge('A', 'D')
graph.addEdge('C', 'D')
graph.addEdge('C', 'G')
graph.addEdge('D', 'G')
graph.addEdge('D', 'H')
graph.addEdge('B', 'E')
graph.addEdge('B', 'F')
graph.addEdge('E', 'I')
console.log(graph.graphToString())
グラフの走査: グラフの各頂点を 1 回訪問します。繰り返しの訪問はできません。
- 幅優先検索 BFS
- 深さ優先トラバーサル (深さ優先探索 DFS)
广度优先遍历:
キューでは、頂点をキューに保存することで、キュー内の最初の頂点が最初に探索されます。幅優先検索アルゴリズムは、一度にグラフの 1 つの層を訪問するのと同じように、最初に指定された頂点から開始してグラフを横断し、最初にその隣接するすべての頂点を訪問します。簡単に言えば、以下の図に示すように、頂点にレイヤーごとにアクセスすることです。
JavaScript で幅優先トラバーサルを実装します。
breadthFirst(v, callback) {
const read = []
const adjList = this.adjList
let pending = [v || this.vertices[0]]
const readVertices = vertices => {
vertices.forEach(key => {
read.push(key)
pending.shift()
adjList[key].forEach(v => {
if (!pending.includes(v) && !read.includes(v)) {
pending.push(v)
}
})
if (callback) callback(key)
if (pending.length) readVertices(pending)
})
}
readVertices(pending)
}
深度优先遍历:
深さ優先検索アルゴリズムは、最初に指定された頂点から開始してグラフを横断し、このパスの最後の頂点に到達するまでパスをたどり、その後バックトラックして次のパスを探索します。言い換えると、次の図に示すように、頂点を深さで訪問し、次に幅で訪問します。
JavaScript での深さ優先トラバーサルの実装:
deepFirst(callback) {
const read = []
const adjList = this.adjList
const readVertices = vertices => {
vertices.forEach(key => {
if (read.includes(key)) return false
read.push(key)
if (callback) callback(key)
if (read.length !== this.vertices.length) {
readVertices(adjList[key])
}
})
}
readVertices(Object.keys(adjList))
}
アルゴリズム:
並べ替え アルゴリズム:
1.冒泡排序:
バブル ソート 隣接する 2 つの項目を比較し、最初の項目が 2 番目の項目より大きい場合はそれらを交換します。最悪の場合、n ラウンドを比較します。n は配列の長さです。
const arr = [2,4,1,9,8,7,6,5,3]
function bubbleSort(arr){
if(!Array.isArray(arr)){
console.log('请输入一个数组!')
return false
}
const len = arr.length
for(let i=0; i<len; i++){
for(let j=0; j<len - i; j++){
if(arr[j] > arr[j+1]){
[arr[j], arr[j+1]] = [arr[j+1], arr[j]]
}
}
}
return arr
}
const res = bubbleSort(arr) // [1, 2, 3, 4, 5,6, 7, 8, 9]
シミュレーション図:
2.快速排序
:
// 1. 选择基准值,一般选择数组中间的值并且向下取整
// 2. 定义左右两个数组,比基准值小的放在左边,比基准值大的放在右边
// 3. 递归左右两个数组
// 4. 终止条件是左右两个数组都只有一个元素
const arr = [49, 38, 65, 97, 76, 13, 27, 49]
function quickSort(arr){
// 终止条件是左右两个数组都只有一个元素
if(arr.length<2){
return arr
}
// 定义左右两个数组
const leftArr = [],
rightArr = []
// 获取基准值
const index = Math.floor(arr.length/2)
const baseValue = arr.splice(index,1)
// 比基准值小的放在左边,比基准值大的放在右边
for(let i of arr){
if(i < baseValue){
leftArr.push(i)
}else{
rightArr.push(i)
}
}
// 递归左右两个数组
return [...quickSort(leftArr), ...baseValue, ...quickSort(rightArr)]
}
const res = quickSort(arr)
console.log(res); // [13, 27, 38, 49,49, 65, 76, 97]
3.选择排序:
データ構造内で最小の値を見つけてそれを最初に置き、次に 2 番目に小さい値を見つけて 2 番目に置き、というように続きます。
// 选择排序:每次找到最小值放在最前面
const arr = [49, 38, 65, 97, 76, 13, 27, 49]
function choseSort(arr){
for(let i=0; i<arr.length; i++){
// 假设最小值元素对应的下标为 i
let minIndex = i
// 寻找最小值
for(let j=i+1; j<arr.length; j++){
if(arr[minIndex] > arr[j]){
minIndex = j
}
}
// 交换数据,第一轮交换第一个数和最小数的位置,以此类推
[arr[i],arr[minIndex]] = [arr[minIndex],arr[i]]
}
return arr
}
const res = choseSort(arr)
console.log(res); // [13, 27, 38, 49,49, 65, 76, 97]
4.插入排序:
ループ数の実際の最適化は、順序付けされていないシーケンスのセットの左側を常に順序付きとして扱うことです。
// 1. 第一次是首位,因为只有一个元素
// 2. 第二次取出数组中第二个元素,从后向前遍历左侧的有序数列,找到第一个比这个数大的数,将取出来的数插到这个数的前面
// 3. 如果没有比取出来的数大的,则将这个数插在左侧有序数列的最后面
// 4. 重复步骤2,3,每次向后取一个元素
const arr = [49, 38, 65, 97, 76, 13, 27, 49]
function insertSort(arr){
for(let i=1; i<arr.length; i++){
// 取出来的目标数
let targetNum = arr[i],j = i-1
// 从后向前遍历
while(arr[j] > targetNum){
// 将目标数插入到比它大的数的前面
arr[j+1] = arr[j];
j--
}
// 没有比目标数大的数,则目标数就在原位,不需要改动
arr[j+1] = targetNum
}
return arr
}
const res = insertSort(arr)
console.log(res); // [13, 27, 38, 49,49, 65, 76, 97]
5.归并排序
: マージ ソートは分割統治アルゴリズムです。このアイデアは、各小さな配列の位置が 1 つだけになるまで元の配列を小さな配列に分割し、並べ替えられる大きな配列が 1 つだけになるまで小さな配列を大きな配列にマージすることです。
// 合并两个有序数组
// 将待排序序列不断地二分,直到只有一个元素。
// 递归地将左右两个子序列进行归并排序。
// 合并两个有序子序列,生成一个新的有序序列。
// 合并地具体操作如下:
// 创建一个临时数组,用于存储合并结果。
// 比较左右两个子序列的第一个元素,将较小的元素放入临时数组,并将对应子序列的指针向后移动一位。
// 重复上述比较和放入操作,直到其中一个子序列为空。
// 将另一个非空子序列的剩余元素依次放入临时数组。
// 将临时数组的元素复制回原始数组相应位置。
function mergeSort(arr) {
if (arr.length <= 1) {
return arr;
}
const mid = Math.floor(arr.length / 2);
const left = arr.slice(0, mid);
const right = arr.slice(mid);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right) {
let result = [];
let i = 0;
let j = 0;
while (i < left.length && j < right.length) {
if (left[i] < right[j]) {
result.push(left[i]);
i++;
} else {
result.push(right[j]);
j++;
}
}
return result.concat(left.slice(i)).concat(right.slice(j));
}
// 示例用法
const arr = [5, 3, 8, 4, 2, 9, 1, 7, 6];
const sortedArr = mergeSort(arr);
console.log(sortedArr); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
検索アルゴリズム:
顺序搜索:
最も基本的な検索アルゴリズムは、データ構造内の各要素と探している要素を比較することですが、この検索アルゴリズムは非常に非効率です。
const arr = [5,4,3,2,1]
const arr = [5,4,3,2,1]
function sequentialSearch(arr, value){
for(let i of arr){
if(i == value)return '搜索到了'
}
return '没有搜索到'
}
const res = sequentialSearch(arr, 3)
console.log(res,'res=='); // 搜索到了
二分搜索
:
// 二分搜索
// 1. 要求被搜索的数据结构已经排序,选择中间值
// 2. 如果中间值等于目标值,则找到,算法执行完毕
// 3. 如果中间值大于目标值,则说明目标值在中间值的左侧,此时在左侧在找到中间值,在比较,以此类推
// 4. 如果中间值小于目标值,则说明目标值在中间值的右侧,此时在右侧在找到中间值,在比较,以此类推
const arr = [1,2,3,4,5,6,7,8]
function BinarySearchTree(arr, value){
// arr是一个已经排序好的数组,如果是无序的先进行排序
let left = 0
let right = arr.length - 1
while(left <= right){
// 中间值的下标
let mid = Math.floor((left + right) / 2)
if(arr[mid] == value){
return `找到了下标为:${
mid}`
}else if(arr[i] > value){
right = mid -1
}else{
left = mid +1
}
}
return '找不到目标值'
}
const res = BinarySearchTree(arr,5) // '找到了下标为:4'
const res = BinarySearchTree(arr,9) // '找不到目标值'
フィボナッチ数列: 最初の 2 つの数値は 1 で、3 番目の数値は最初の 2 つの数値の合計です。
function fibonacci(n){
// 要求n是大于0的整数
if(n < 3){
return 1
}else{
return fibonacci(n-1) + fibonacci(n-2)
}
}
fibonacci(1) // 1
fibonacci(2) // 1
fibonacci(3) // 2
fibonacci(6) // 8
階乗数列: 現在の数値は、1 から現在の数値を乗算したものと等しくなります。例: f(3)=1 2 3
function factorial(n){
// 要求n是大于0的整数
if(n == 1){
return 1
}else{
return factorial(n-1) * n
}
}
factorial(1) // 1
factorial(2) // 2
factorial(3) // 6
factorial(4) // 24
factorial(5) // 120
分割統治: 複雑な問題を複数の比較的単純で独立したサブ問題に分解し、これらのサブ問題を再帰的に解決し、最後にそれらを組み合わせて元の問題の解を取得します。具体的な手順は次のとおりです。
分解(Divide):
同じまたは類似した複数のサブ問題。このステップは通常、再帰を使用して実装されます。
解决(Conquer):
部分問題を再帰的に解決します。部分問題が十分に小さい場合は、直接解決できます。
合并(Combine):
部分問題の解決策を元の問題の解決策に結合します。
例: マージソート、クイックソート
ダイナミック プログラミング: 複雑な問題を相互に関連する複数のサブ問題に分解し、サブ問題を繰り返し解くことで元の問題を解決します。
一般的な動的計画法の問題は、フィボナッチ数列、階段の上り下り、家強盗、ナップザック問題、最長共通部分列問題、コインの小銭、グラフの全ソース最短経路などです。
爬楼梯问题:
あなたが階段を登っているとします。建物の屋上に到達するまでに n 歩かかります。一度に1段か2段ずつ登ることができます。建物の屋上に行くには、何通りの方法がありますか?
// 动态规划,爬楼梯
// 示例 1:
// 输入:n=2
// 输出:2
// 解释:有两种爬楼梯的方法:
// 1. 1 阶 + 1 阶
// 2. 2 阶
// 示例 2:
// 输入:n = 3
// 输出:3
// 解释:有三种方法可以爬到楼顶。
// 1. 1 阶 + 1 阶 + 1 阶
// 2. 1 阶 + 2 阶
// 3. 2 阶 + 1 阶
// 解题思路:可以假设在第n-1个台阶爬一个台阶,或者在第n-2个台阶爬两个台阶。则问题就变为了:f(n)=f(n-1) + f(n-2)
// f(n-1) 为爬到第n-1个台阶的办法,f(n-2)为爬到第n-2个台阶的办法
// 时间复杂度O(n)
// 空间复杂度O(n)
function climbStairs(n){
// 规定n是大于等于1的正整数
if(n==1){
return 1
}else{
// 存放f(n)的数组
let dp = [1,1]
for(let i=2;i<=n;i++){
dp[i] = dp[i-1] + dp[i-2]
}
return dp[n]
}
}
// 优化算法:变量替换数组
// 时间复杂度O(n)
// 空间复杂度O(1)
function climbStairs(n){
if(n==1) return 1
let dp0 = 1,dp1 = 1
for(let i=2;i<=n;i++){
[dp0, dp1] = [dp1, dp0 + dp1]
}
return dp1
}
climbStairs(5) // 8
climbStairs(8) // 34
取值问题
: 隣接する2つの部屋を同時に通過せずに獲得できる最大金額
// 示例 1:
// 输入:[1,2,3,1]
// 输出:4
// 解释:1 号房屋 (金额 = 1) ,然后 3 号房屋 (金额 = 3)。
// 获取到的最高金额 = 1 + 3 = 4 。
// 示例 2:
// 输入:[2,7,9,3,1]
// 输出:12
// 解释:1 号房屋 (金额 = 2), 3 号房屋 (金额 = 9),接着5 号房屋 (金额 = 1)。
// 获取到的最高金额 = 2 + 9 + 1 = 12 。
// 解题思路:到第 k 间房能获取到的最大金额,应该是前 k-2 间房获取到的最大金额加上第 k 间房的金额。
// 或者是 前 k-1 间房获取到的最大金额。
// 最终结果就是这两种情况下取最大值。
// f(k): 前 k 间房获取的最大值,a(k): 第 k 间房的金额。
// f(k) = Math.max(f(k-2) + a(k), f(k-1))
function getmoney(list){
if(list.length == 0)return 0
// 存放能获取到的最大金额的数组,i表示房间数
let dp = [0, list[0]]
for(let i=2;i<=list.length;i++){
dp[i] = Math.max(dp[i-2] + list[i-1], dp[i-1])
}
return dp[list.length]
}
getmoney([2,5,3,1,9,4,7,6]) // 21
getmoney([2,8,3,1,9,4,7,6]) // 24
貪欲なアルゴリズム:期望通过每一个阶段的局部最优解,从而得到全局最优解(
これは単なる期待であり、実際には大域的な最適解が得られない可能性があります)。動的プログラミングのようなより大きなパターンは計算されません。
貪欲アルゴリズムと動的プログラミングの類似点と相違点:
類似点:
- どちらも最適な部分構造を持ち、貪欲な選択戦略 (greedy) または重複する部分問題 (dp) のいずれかを持ちます。
- 貪欲アルゴリズムは特別な動的計画法アルゴリズムであることが理解できます。
違い:
- 貪欲アルゴリズムは適用範囲が少なく、局所的な最適解を解決します。各ステージの最適なソリューションは、前のステージに依存します。得られる最終的な解は、必ずしも大域的な最適解ではなく、すべての局所的な最適解の推論であり、解プロセスは高速であり、トップダウンの解プロセスです。
- 動的プログラミング: 網羅的手法と同様に、すべての実現可能な解を解き、最終的にバックトラッキング手法を通じて大域的な最適解を取得します。
- 貪欲アルゴリズムの解決プロセス:
特定のルールに従ってすべての候補解を並べ替え、
貪欲な選択戦略 (特定のループ実装) に従って最適解として選択されるかどうかを判断します。 - 動的計画法による解法プロセス:
①問題に対して実行可能なすべての解をリストに格納し、
②再帰式(再帰的に記述できる部分)を循環記述し、
③バックトラッキング法を使用して最適解を導き出します。
const coinArr = [1,0.5,5,2,10,50,20,100]
const amount = 43
function changeAmount(coins, amount){
// 把钱币面额数组从大到小排序
coins.sort((a,b)=>b-a)
// 存储找零结果
let result = []
for (let i = 0;i<coinArr.length;i++){
while(amount>=coins[i]){
// 将最大面值放进数组
result.push(coinArr[i])
// 减去已经找零的金额
amount -= coinArr[i]
}
}
// 能找开的时候amount一定是等于零的
if(amount>0){
return '找不开零钱'
}
return result
}
console.log(changeAmount(coinArr,amount));