集合

什么是数据结构与算法?

  • 数据结构就是在计算机中,存储和组织数据的方式。
    + 常见的数据结构:
    <img src="http://vamknight.com/%E5%B8%B8%E8%A7%81%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.png">
    <!--more-->
    
  • 算法(Algorithm)的定义:
    • 算法就是解决问题的方法/步骤,数据结构的实现离不开算法。
      • 一个有限指令集,每条指令的描述不依赖于语言
      • 接受一些输入(有些情况不需要输入)
      • 产生输出
      • 一定在有限步骤之后终止

集合

  • 集合通常是由一组无序的,不能重复的元素构成。可以看成是一种特殊的数组,特殊之处在于里
    面的元素没有顺序就意味着不能通过下标值进行访问,不能重复意味着相同的对象在同一个集合中只能存
    在一份。

  • 集合都有哪些常见的操作方法呢?

    • add(value):向集合添加一个新的项。

    • remove(value):从集合移除一个值。

    • has(value):如果值在集合中,返回 true,否则返回 false。

    • clear():移除集合中的所有项。

    • size():返回集合所包含元素的数量。与数组的 length 属性类似。

    • values():返回一个包含集合中所有值的数组。

  • 集合之间都有哪些操作呢?

    • 并集:对于两个给定的集合,返回一个包含两个集合中所有元素的新集合。
    • 交集:对于两个给定的集合,返回一个包含两个集合中公有元素的新集合。
    • 差集:对于两个给定的集合,返回一个包含所有存在于第一个集合且不存在于第二个集合的新集合。
    • 子集:验证一个给定集合是否是另一个集合的子集。
  • 集合封装的完整代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// 封装集合的构造函数
function Set() {
// 使用一个对象来保存集合的元素
this.items = {}

// 集合的操作方法
// 判断集合中是否有某个元素
Set.prototype.has = function (value) {
return this.items.hasOwnProperty(value)
}

// 向集合中添加元素
Set.prototype.add = function (value) {
// 1.判断集合中是否已经包含了该元素
if (this.has(value)) return false

// 2.将元素添加到集合中
this.items[value] = value
return true
}

// 从集合中删除某个元素
Set.prototype.remove = function (value) {
// 1.判断集合中是否包含该元素
if (!this.has(value)) return false

// 2.包含该元素, 那么将元素删除
delete this.items[value]
return true
}

// 清空集合中所有的元素
Set.prototype.clear = function () {
this.items = {}
}

// 获取集合的大小
Set.prototype.size = function () {
return Object.keys(this.items).length

/*
考虑兼容性问题, 使用下面的代码
var count = 0
for (var value in this.items) {
if (this.items.hasOwnProperty(value)) {
count++
}
}
return count
*/
}

// 获取集合中所有的值
Set.prototype.values = function () {
return Object.keys(this.items)

/*
考虑兼容性问题, 使用下面的代码
var keys = []
for (var value in this.items) {
keys.push(value)
}
return keys
*/
}

//集合之间的操作
//并集
Set.prototype.union = function (otherSet) {
//this:集合对象A
//otherSet:集合对象B
//1.创建新的集合
var unionSet = new Set()

//2.将A集合中所有的元素添加到新集合中
var values = this.values()
for(var i = 0; i < values.length; i++) {
unionSet.add(values[i])
}

//3.取出B集合中的元素,判断是否需要添加到新集合
values = otherSet.values()
for(var i = 0; i < values.length; i++) {
unionSet.add(values[i])
}
return unionSet
}

//交集
Set.prototype.intersection = function (otherSet) {
var intersectionSet = new Set()
var values = this.values()
//取出A集合一个个元素,判断是否同时存在于B中,存在B中,则添加到新集合中
for (var i = 0; i < values.length; i++) {
var item = values[i]
if (otherSet.has(item)) {
intersectionSet.add(item)
}
}
return intersectionSet
}

//差集
Set.prototype.difference = function (otherSet) {
var differenceSet = new Set()
var values = this.values()
//取出A集合一个个元素,判断是否同时存在于B中,不存在B中,则添加到新集合中
for (var i = 0; i < values.length; i++) {
var item = values[i]
if (!otherSet.has(item)) {
differenceSet.add(item)
}
}
return differenceSet
}

// 子集
Set.prototype.subset = function (otherSet) {
var values = this.values()
for (var i = 0; i < values.length; i++) {
var item = values[i]
if (!otherSet.has(item)) {
return false
}
}
return true
}
}

队列(Queue)

  • 队列是一种受限的线性表,先进先出(FIFO First In First Out)。

    • 它只允许在表的前端(front)进行删除操作
    • 在表的后端(rear)进行插入操作
  • 常见应用场景: + 队列主要用在和时间有关的地方,特别是操作系统中,队列是实现多任务的重要机制。 + 消息机制可以通过队列来实现,进程调度也是使用队列来实现。

  • 队列有哪些常见的操作呢?

    • enqueue(element): 向队列尾部添加一个(或多个)新的项。
    • dequeue(): 移除队列的第一项,并返回被移除的元素。
    • front(): 返回队列中第一个元素,队列不做任何改动。
    • isEmpty(): 如果队列中不包含任何元素,返回 true,否则返回 false。
    • size(): 返回队列包含的元素个数,与数组 length 类似。
    • toString(): 将队列中的内容,转成字符串形式。
  • 队列常见操作的封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//封装队列
function Queue() {
//属性
this.items = []
//将元素加入到队列中
Queue.prototype.enqueue = function(element) {
this.items.push(element)
}
//从队列中删除前端元素
Queue.prototype.dequeue = function() {
return this.items.shift()
}
//查看前端的元素
Queue.prototype.front = function() {
return this.items[0]
}
//查看队列是否为空
Queue.prototype.isEmpty = function() {
return this.items.length == 0
}
//查看队列中元素的个数
Queue.prototype.size = function() {
return this.items.length
}
//toString方法
Queue.prototype.toString = function() {
var resultString = ''
for (var i = 0; i< this.items.length; i++) {
resultString += this.items[i] + ' '
}
return resultString
}
}

//调用队列函数
var queue = new Queue()
queue.enqueue(20)
alert(queue)
  • 面试题: 击鼓传花
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//面试题: 击鼓传花
function passGame(nameList, num) {
//创建一个队列结构
var queue = new Queue()
//将所有人加入到队列中
for(var i = 0; i < nameList.length; i++) {
queue.enqueue(nameList[i])
}
//开始数数字
while (queue.size() > 1) {
//不是num重新加入队列末尾
//是num从队列中删除
for (var i = 0; i< num - 1; i++) {
queue.enqueue(queue.dequeue())
}
queue.dequeue()
}
return queque.front()
}
  • 封装优先队列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//封装优先级队列
function PriorityQueue() {
//内部创建一个构造类
function QueueElement(element, priority) {
this.element = element
this.priority = priority
}

//属性
this.items = []

//实现队列元素的插入
PriorityQueue.prototype.enqueue = function(element, priority) {
//创建QueueElement对象
var queueElement = new QueueElement(element, priority)

//判断为队列是否为空
if (this.items.length == 0) {
this.items.push(queueElement)
} else {
var added = false
for (var i = 0; i < this.items.length; i++) {
if (queueElement.priority < this.items[i].priority) {
this.items.splice(i, 0, queueElement)
added = true
break
}
}
if (!added) {
this.items.push(queueElement)
}
}
}
//从队列中删除前端元素
PriorityQueue.prototype.dequeue = function() {
return this.items.shift()
}
//查看前端的元素
PriorityQueue.prototype.front = function() {
return this.items[0]
}
//查看队列是否为空
PriorityQueue.prototype.isEmpty = function() {
return this.items.length == 0
}
//查看队列中元素的个数
PriorityQueue.prototype.size = function() {
return this.items.length
}
//toString方法
PriorityQueue.prototype.toString = function() {
var resultString = ''
for (var i = 0; i< this.items.length; i++) {
resultString += this.items[i].element + '-' + this.items[i].priority + ' '
}
return resultString
}
}

//测试代码
var pq = new PriorityQueue()
pq.enqueue('a',10)
pq.enqueue('b',100)
pq.enqueue('c',50)
alert(pq)

链表

  • 什么是链表? + 链表的元素在内存中不必是连续的空间,链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用组成。

  • 相对于数组,链表都有哪些优势?

    • 内存空间不是比是连续的. 可以充分利用计算机的内存. 实现灵活的内存动态管理。
    • 链表不必在创建时就确定大小, 并且大小可以无限的延伸下去。
    • 链表在插入和删除数据时, 时间复杂度可以达到 O(1). 相对数组效率高很多。
  • 相对于数组,链表都有哪些缺点? + 链表访问任何一个位置的元素时, 都需要从头开始访问.(无法跳过第一个元素访问任何一个元素)。 + 无法通过下标直接访问元素, 需要从头一个个访问, 直到找到对应的问题。

  • 链表的数据结构:

  • 链表有哪些常见操作? + append(element):向列表尾部添加一个新的项

    + insert(position, element):向列表的特定位置插入一个新的项。
    
    + update(position, element): 修改某一个位置上的元素。
    
    + remove(element):从列表中移除一项。
    
    + indexOf(element):返回元素在列表中的索引。如果列表中没有该元素则返回-1。
    
    // 链表中的属性
    this.length = 0
    this.head = null
    
    // 链表尾部追加元素方法
    LinkedList.prototype.append = function (element) {
        // 1.根据新元素创建节点
        var newNode = new Node(element)
    
        // 2.判断原来链表是否为空
        if (this.head === null) { /
    + removeAt(position):从列表的特定位置移除一项。
    

    this.next = null
    }
    / 链表尾空
    this.head = newNode
    } else { // 链表不为空
    // 2.1.定义变量, 保存当前找到的节点
    var current = this.head
    while (current.next) {
    current = current.next
    }

            // 2.2.找到最后一项, 将其next赋值为node
            current.next = newNode
        }
    
        // 3.链表长度增加1
        this.length++
    }
    
    // 链表的toString方法
    LinkedList.prototype.toString = function () {
        // 1.定义两个变量
        var current = this.head
        var listString = ""
    
        // 2.循环获取链表中所有的元素
        while (current) {
            listString += "," + current.element
            current = current.next
        }
    
        // 3.返回最终结果
        return listString.slice(1)
    }
    
    // 根据下标删除元素
    LinkedList.prototype.insert = function (position, element) {
        // 1.检测越界问题: 越界插入失败
        if (position < 0 || position > this.length) return false
    
        // 2.定义变量, 保存信息
        var newNode = new Node(element)
        var current = this.head
        var previous = null
        index = 0
    
        // 3.判断是否列表是否在第一个位置插入
        if (position == 0) {
            newNode.next = current
            this.head = newNode
        } else {
            while (index++ < position) {
                previous = current
                current = current.next
            }
    
            newNode.next = current
            previous.next = newNode
        }
    
        // 4.length+1
        this.length++
    
        return true
    }
    
    //update方法
    LinkedList.prototype.update = function (position, newData) {
        if (position < 0 || position >= this.length) return  null
    
        var current = this.head
        var index = 0
        while (index++ < position) {
            current = current.next
        }
        current.data = newData
        return true
    }
    
    // 根据位置移除节点
    LinkedList.prototype.removeAt = function (position) {
        // 1.检测越界问题: 越界移除失败, 返回null
        if (position < 0 || position >= this.length) return null
    
        // 2.定义变量, 保存信息
        var current = this.head
        var previous = null
        var index = 0
    
        // 3.判断是否是移除第一项
        if (position === 0) {
            this.head = current.next
        } else {
            while (index++ < position) {
                previous = current
                current = current.next
            }
    
            previous.next = current.next
        }
    
        // 4.length-1
        this.length--
    
        // 5.返回移除的数据
        return current.element
    }
    
    // 根据元素获取链表中的位置
    LinkedList.prototype.indexOf = function (element) {
        // 1.定义变量, 保存信息
        var current = this.head
        index = 0
    
        // 2.找到元素所在的位置
        while (current) {
            if (current.element === element) {
                return index
            }
            index++
            current = current.next
        }
    
        // 3.来到这个位置, 说明没有找到, 则返回-1
        return -1
    }
    
    // 根据元素删除信息
    LinkedList.prototype.remove = function (element) {
        var index = this.indexOf(element)
        return this.removeAt(index)
    }
    
    // 判断链表是否为空
    LinkedList.prototype.isEmpty = function () {
        return this.length == 0
    }
    
    // 获取链表的长度
    LinkedList.prototype.size = function () {
        return this.length
    }
    
    // 获取第一个节点
    LinkedList.prototype.getFirst = function () {
        return this.head.element
    }
    

    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
- 以上操作的都是单向链表,下面来认识一下双向链表。
- 单向链表的缺点:
+ 只能从头遍历到尾,也就是链表的相连的过程是单向的,实现的原理是上一个链表中有一个指向下一个的引用。
+ 我们可以轻松的到达下一个节点,但是回到上一个节点是很难的,只能从头遍历。
+ 但是,实际开发中,经常会遇到回到上一个节点的情况。
- 双向链表:
+ 既可以从头遍历到尾,又可以从尾遍历到头。
+ 也就是链表相连的过程是双向的。
+ 实现的原理就是既有先前连接的引用,也有一个向后连接的引用。
+ 双向链表可以有效的解决单向链表的问题。
- 双向链表的一些缺点:
+ 每次在插入或删除某一个节点时,需要处理四个引用,实现起来比较复杂。
+ 相对于单向链表占用的内存更大一些。

- 双向链表的结构图:
<img src="http://vamknight.com/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8.png">

- 双向链表的特点:
+ 可以使用一个head和一个tail分别指向头部和尾部的节点。
+ 每个节点都是由三部分组成:前一个节点的指针(prev)、保存的元素(item)、后一个节点的指针(next)。
+ 双向链表的第一个节点的prev是null。
+ 双向链表的最后一个节点的next是null。

- 双向链表都有哪些常见操作?
+ append(element):向列表尾部添加一个新的项

+ insert(position, element):向列表的特定位置插入一个新的项。

+ update(position, element): 修改某一个位置上的元素。

+ get(position): 获取对应位置的元素。

+ remove(element):从列表中移除一项。

+ indexOf(element):返回元素在列表中的索引。如果列表中没有该元素则返回-1。

+ removeAt(position):从列表的特定位置移除一项。

+ isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false。

+ size():返回链表包含的元素个数。与数组的length属性类似。

+ toString():由于列表项使用了Node类,就需要重写继承自JavaScript对象默认的toString方法,让其只输出元素的值。

+ forwardString(): 返回正向遍历的节点字符串形式。

+ backwardString(): 返回反向遍历的节点字符串形式。

- 双向链表常见方法的封装:

// 创建双向链表的构造函数
function DoublyLinkedList() {
// 创建节点构造函数
function Node(element) {
this.element = element
this.next = null
this.prev = null // 新添加的
}

// 定义属性
this.length = 0
this.head = null
this.tail = null // 新添加的

// 定义相关操作方法
// 在尾部追加数据
DoublyLinkedList.prototype.append = function (element) {
    // 1.根据元素创建节点
    var newNode = new Node(element)

    // 2.判断列表是否为空列表
    if (this.head == null) {
        this.head = newNode
        this.tail = newNode
    } else {
        this.tail.next = newNode
        newNode.prev = this.tail
        this.tail = newNode
    }

    // 3.length+1
    this.length++
}

// 在任意位置插入数据
DoublyLinkedList.prototype.insert = function (position, element) {
    // 1.判断越界的问题
    if (position < 0 || position > this.length) return false

    // 2.创建新的节点
    var newNode = new Node(element)

    // 3.判断插入的位置
    if (position === 0) { // 在第一个位置插入数据
        // 判断链表是否为空
        if (this.head == null) {
            this.head = newNode
            this.tail = newNode
        } else {
            this.head.prev = newNode
            newNode.next = this.head
            this.head = newNode
        }
    } else if (position === this.length) { // 插入到最后的情况
        // 思考: 这种情况是否需要判断链表为空的情况呢? 答案是不需要, 为什么?
        this.tail.next = newNode
        newNode.prev = this.tail
        this.tail = newNode
    } else { // 在中间位置插入数据
        // 定义属性
        var index = 0
        var current = this.head
        var previous = null

        // 查找正确的位置
        while (index++ < position) {
            previous = current
            current = current.next
        }

        // 交换节点的指向顺序
        newNode.next = current
        newNode.prev = previous
        current.prev = newNode
        previous.next = newNode
    }

    // 4.length+1
    this.length++

    return true
}

// 根据位置删除对应的元素
DoublyLinkedList.prototype.removeAt = function (position) {
    // 1.判断越界的问题
    if (position < 0 || position >= this.length) return null

    // 2.判断移除的位置
    var current = this.head
    if (position === 0) {
        if (this.length == 1) {
            this.head = null
            this.tail = null
        } else {
            this.head = this.head.next
            this.head.prev = null
        }
    } else if (position === this.length -1) {
        current = this.tail
        this.tail = this.tail.prev
        this.tail.next = null
    } else {
        var index = 0
        var previous = null

        while (index++ < position) {
            previous = current
            current = current.next
        }

        previous.next = current.next
        current.next.prev = previous
    }

    // 3.length-1
    this.length--

    return current.element
}

// 根据元素获取在链表中的位置
DoublyLinkedList.prototype.indexOf = function (element) {
    // 1.定义变量保存信息
    var current = this.head
    var index = 0

    // 2.查找正确的信息
    while (current) {
        if (current.element === element) {
            return index
        }
        index++
        current = current.next
    }

    // 3.来到这个位置, 说明没有找到, 则返回-1
    return -1
}

// 根据元素删除
DoublyLinkedList.prototype.remove = function (element) {
    var index = this.indexOf(element)
    return this.removeAt(index)
}

// 判断是否为空
DoublyLinkedList.prototype.isEmpty = function () {
    return this.length === 0
}

// 获取链表长度
DoublyLinkedList.prototype.size = function () {
    return this.length
}

// 获取第一个元素
DoublyLinkedList.prototype.getHead = function () {
    return this.head.element
}

// 获取最后一个元素
DoublyLinkedList.prototype.getTail = function () {
    return this.tail.element
}

// 遍历方法的实现
// 正向遍历的方法
DoublyLinkedList.prototype.forwardString = function () {
    var current = this.head
    var forwardStr = ""

    while (current) {
        forwardStr += "," + current.element
        current = current.next
    }

    return forwardStr.slice(1)
}

// 反向遍历的方法
DoublyLinkedList.prototype.reverseString = function () {
    var current = this.tail
    var reverseStr = ""

    while (current) {
        reverseStr += "," + current.element
        current = current.prev
    }

    return reverseStr.slice(1)
}

// 实现toString方法
DoublyLinkedList.prototype.toString = function () {
    return this.forwardString()
}

}

1

  • 我们知道数组是一种线性结构,可以在数组的任意位置插入或删除数据。但有些时候,我们为了实现某种功能,必须对这种 任意性 加以限制,而我们的栈和队列就是比较常见的 受限的线性结构

  • 栈是一种先进后出或**后进先出(LIFO Last In First Out)**的数据结构,栈内的元素只能通过列表的一端访问,这一端称为栈顶,因为
    数据只能在栈顶添加或删除,所以只要数据的保存满足“先进后出或后进先出”的原理,都优先考虑使用栈。

  • 栈的结构示意图:

  • 栈常见有哪些操作?

  • push(element):添加一个新元素到栈顶位置。
  • pop():移除栈顶的元素,同时返回被移除的元素。
  • peek():返回栈顶的元素,不对栈做任何修改。
  • isEmpty():如果栈里没有任何元素返回 true,否则返回 false。
  • size():返回栈里的元素个数,类似数组中的 length。
  • toString():将栈结构的内容以字符形式返回。
  • 栈常见操作的封装:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function Stack() {
this.items = []
//将元素压入栈
Stack.prototype.push = function(element) {
this.items.push(element)
}
//从栈中移除元素
Stack.prototype.pop = function() {
return this.items.pop()
}
//查看栈顶元素
Stack.prototype.peek = function() {
return this.items[this.items.length - 1]
}
//判断栈是否为空
Stack.prototype.isEmpty = function() {
return this.items.length == 0
}
//判断栈中元素个数
Stack.prototype.size = function() {
return this.items.length
}
//toString方法
Stack.prototype.toString = function() {
var resultString = ''
for (var i = 0; i< this.items.length; i++) {
resultString += this.items[i] + ' '
}
return resultString
}
}
//栈的使用
var s = new Stack()
s.push(23)
alert(s)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//函数十进制转为二进制
function dec2bin(decNumber) {
//定义栈对象
var stack = new Stack()
while (decNumber > 0) {
//获取余数放入栈中
stack.push(decNumber % 2)
//获取除后的结果,作为下次操作的对象
decNumber = Math.floor(decNumber / 2)
}
//从栈中取出0和1
var binaryString = ''
while (!stack.isEmpty()) {
binaryString += stack.pop()
}
return binaryString
}
//调用函数
alert(dec2bin(100))

数据结构与算法

五大算法

  • 贪心算法

  • 分治算法

  • 动态规划

  • 回溯法

  • 分支限界法

冒泡排序

1
2
3
4
5
6
7
8
9
10
11
function bubleSort(arr) {
var len = arr.length;
for (let outer = len ; outer >= 2; outer--) {
for(let inner = 0; inner <=outer - 1; inner++) {
if(arr[inner] > arr[inner + 1]) {
[arr[inner],arr[inner+1]] = [arr[inner+1],arr[inner]]
}
}
}
return arr;
}

选择排序

1
2
3
4
5
6
7
8
9
10
11
function selectSort(arr) {
var len = arr.length;
for(let i = 0 ;i < len - 1; i++) {
for(let j = i ; j<len; j++) {
if(arr[j] < arr[i]) {
[arr[i],arr[j]] = [arr[j],arr[i]];
}
}
}
return arr
}

插入排序

1
2
3
4
5
6
7
8
9
10
11
12
function insertSort(arr) {
for(let i = 1; i < arr.length; i++) { //外循环从1开始,默认arr[0]是有序段
for(let j = i; j > 0; j--) { //j = i,将arr[j]依次插入有序段中
if(arr[j] < arr[j-1]) {
[arr[j],arr[j-1]] = [arr[j-1],arr[j]];
} else {
break;
}
}
}
return arr;
}

快速排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function quickSort(arr) {
if(arr.length <= 1) {
return arr; //递归出口
}
var left = [],
right = [],
current = arr.splice(0,1);
for(let i = 0; i < arr.length; i++) {
if(arr[i] < current) {
left.push(arr[i]) //放在左边
} else {
right.push(arr[i]) //放在右边
}
}
return quickSort(left).concat(current,quickSort(right));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
//创建列表类
function ArrayList() {
this.array = []
//方法
//插入方法
ArrayList.prototype.insert = function(item){
this.array.push(item)
}
//toString 方便测试
ArrayList.prototype.toString = function(){
return this.array.join('-')
}
}
<!--var arr = new ArrayList()-->
<!--arr.insert(1)-->
<!--arr.insert(2)-->
<!--arr.insert(3)-->

//实现排序算法
//冒泡排序
ArrayList.prototype.bubbleSort = function () {
// 1.获取数组的长度
var length = this.array.length

// 2.反向循环, 因此次数越来越少
for (var i = length - 1; i >= 0; i--) {
// 3.根据i的次数, 比较循环到i位置
for (var j = 0; j < i; j++) {
// 4.如果j位置比j+1位置的数据大, 那么就交换
if (this.array[j] > this.array[j+1]) {
// 交换
this.swap(j, j+1)
}
}
}
}

ArrayList.prototype.swap = function (m, n) {
var temp = this.array[m]
this.array[m] = this.array[n]
this.array[n] = temp
}

//选择排序
ArrayList.prototype.selectionSort = function () {
// 1.获取数组的长度
var length = this.array.length

// 2.外层循环: 从0位置开始取出数据, 直到length-2位置
for (var i = 0; i < length - 1; i++) {
// 3.内层循环: 从i+1位置开始, 和后面的内容比较
var min = i
for (var j = min + 1; j < length; j++) {
// 4.如果i位置的数据大于j位置的数据, 那么记录最小的位置
if (this.array[min] > this.array[j]) {
min = j
}
}
// 5.交换min和i位置的数据
this.swap(min, i)
}
}
//插入排序
ArrayList.prototype.insertionSort = function () {
// 1.获取数组的长度
var length = this.array.length

// 2.外层循环: 外层循环是从1位置开始, 依次遍历到最后
for (var i = 1; i < length; i++) {
// 3.记录选出的元素, 放在变量temp中
var j = i
var temp = this.array[i]

// 4.内层循环: 内层循环不确定循环的次数, 最好使用while循环
while (j > 0 && this.array[j-1] > temp) {
this.array[j] = this.array[j-1]
j--
}

// 5.将选出的j位置, 放入temp元素
this.array[j] = temp
}
}
//希尔排序
ArrayList.prototype.shellSort = function () {
// 1.获取数组的长度
var length = this.array.length

// 2.根据长度计算增量
var gap = Math.floor(length / 2)

// 3.增量不断变量小, 大于0就继续排序
while (gap > 0) {
// 4.实现插入排序
for (var i = gap; i < length; i++) {
// 4.1.保存临时变量
var j = i
var temp = this.array[i]

// 4.2.插入排序的内层循环
while (j > gap - 1 && this.array[j - gap] > temp) {
this.array[j] = this.array[j - gap]
j -= gap
}

// 4.3.将选出的j位置设置为temp
this.array[j] = temp
}

// 5.重新计算新的间隔
gap = Math.floor(gap / 2)
}
}
//快速排序
// 选择枢纽
ArrayList.prototype.median = function (left, right) {
// 1.求出中间的位置
var center = Math.floor((left + right) / 2)

// 2.判断并且进行交换
if (this.array[left] > this.array[center]) {
this.swap(left, center)
}
if (this.array[center] > this.array[right]) {
this.swap(center, right)
}
if (this.array[left] > this.array[right]) {
this.swap(left, right)
}

// 3.巧妙的操作: 将center移动到right - 1的位置.
this.swap(center, right - 1)

// 4.返回pivot
return this.array[right - 1]
}

// 快速排序实现
ArrayList.prototype.quickSort = function () {
this.quickSortRec(0, this.array.length - 1)
}

ArrayList.prototype.quickSortRec = function (left, right) {
// 0.递归结束条件
if (left >= right) return

// 1.获取枢纽
var pivot = this.median(left, right)

// 2.开始进行交换
// 2.1.记录左边开始位置和右边开始位置
var i = left
var j = right - 1
// 2.2.循环查找位置
while (true) {
while (this.array[++i] < pivot) { }
while (this.array[--j] > pivot) { }
if (i < j) {
// 2.3.交换两个数值
this.swap(i, j)
} else {
// 2.4.当i<j的时候(一定不会=, 看下面解释中的序号3), 停止循环因为两边已经找到了相同的位置
break
}
}

// 3.将枢纽放在正确的位置
this.swap(i, right - 1)

// 4.递归调用左边
this.quickSortRec(left, i - 1)
this.quickSortRec(i + 1, right)
}

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// 封装ArrayList
function ArrayList() {
this.array = []

ArrayList.prototype.insert = function (item) {
this.array.push(item)
}

ArrayList.prototype.toString = function () {
return this.array.join()
}

ArrayList.prototype.bubbleSort = function () {
// 1.获取数组的长度
var length = this.array.length

// 2.反向循环, 因此次数越来越少
for (var i = length - 1; i >= 0; i--) {
// 3.根据i的次数, 比较循环到i位置
for (var j = 0; j < i; j++) {
// 4.如果j位置比j+1位置的数据大, 那么就交换
if (this.array[j] > this.array[j+1]) {
// 交换
this.swap(j, j+1)
}
}
}
}

ArrayList.prototype.selectionSort = function () {
// 1.获取数组的长度
var length = this.array.length

// 2.外层循环: 从0位置开始取出数据, 直到length-2位置
for (var i = 0; i < length - 1; i++) {
// 3.内层循环: 从i+1位置开始, 和后面的内容比较
var min = i
for (var j = min + 1; j < length; j++) {
// 4.如果i位置的数据大于j位置的数据, 记录最小的位置
if (this.array[min] > this.array[j]) {
min = j
}
}
this.swap(min, i)
}
}

ArrayList.prototype.insertionSort = function () {
// 1.获取数组的长度
var length = this.array.length

// 2.外层循环: 外层循环是从1位置开始, 依次遍历到最后
for (var i = 1; i < length; i++) {
// 3.记录选出的元素, 放在变量temp中
var j = i
var temp = this.array[i]

// 4.内层循环: 内层循环不确定循环的次数, 最好使用while循环
while (j > 0 && this.array[j-1] > temp) {
this.array[j] = this.array[j-1]
j--
}

// 5.将选出的j位置, 放入temp元素
this.array[j] = temp
}
}

ArrayList.prototype.shellSort = function () {
// 1.获取数组的长度
var length = this.array.length

// 2.根据长度计算增量
var gap = Math.floor(length / 2)

// 3.增量不断变量小, 大于0就继续排序
while (gap > 0) {
// 4.实现插入排序
for (var i = gap; i < length; i++) {
// 4.1.保存临时变量
var j = i
var temp = this.array[i]

// 4.2.插入排序的内存循环
while (j > gap - 1 && this.array[j - gap] > temp) {
this.array[j] = this.array[j - gap]
j -= gap
}

// 4.3.将选出的j位置设置为temp
this.array[j] = temp
}

// 5.重新计算新的间隔
gap = Math.floor(gap / 2)
}
}

ArrayList.prototype.swap = function (m, n) {
var temp = this.array[m]
this.array[m] = this.array[n]
this.array[n] = temp
}

// 选择枢纽
ArrayList.prototype.median = function (left, right) {
// 1.求出中间的位置
var center = Math.floor((left + right) / 2)

// 2.判断并且进行交换
if (this.array[left] > this.array[center]) {
this.swap(left, center)
}
if (this.array[center] > this.array[right]) {
this.swap(center, right)
}
if (this.array[left] > this.array[right]) {
this.swap(left, right)
}

// 3.巧妙的操作: 将center移动到right - 1的位置.
this.swap(center, right - 1)

// 4.返回pivot
return this.array[right - 1]
}

// 快速排序实现
ArrayList.prototype.quickSort = function () {
this.quickSortRec(0, this.array.length - 1)
}

ArrayList.prototype.quickSortRec = function (left, right) {
// 0.递归结束条件
if (left >= right) return

// 1.获取枢纽
var pivot = this.median(left, right)

// 2.开始进行交换
var i = left
var j = right - 1
while (true) {
while (this.array[++i] < pivot) { }
while (this.array[--j] > pivot) { }
if (i < j) {
this.swap(i, j)
} else {
break
}
}

// 3.将枢纽放在正确的位置
this.swap(i, right - 1)

// 4.递归调用左边
this.quickSortRec(left, i - 1)
this.quickSortRec(i + 1, right)
}
}

哈希表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// 创建HashTable构造函数
function HashTable() {
// 定义属性
this.storage = []
this.count = 0
this.limit = 8

// 定义相关方法
// 判断是否是质数
HashTable.prototype.isPrime = function (num) {
var temp = parseInt(Math.sqrt(num))
// 2.循环判断
for (var i = 2; i <= temp; i++) {
if (num % i == 0) {
return false
}
}
return true
}

// 获取质数
HashTable.prototype.getPrime = function (num) {
while (!isPrime(num)) {
num++
}
return num
}

// 哈希函数
HashTable.prototype.hashFunc = function(str, max) {
// 1.初始化hashCode的值
var hashCode = 0

// 2.霍纳算法, 来计算hashCode的数值
for (var i = 0; i < str.length; i++) {
hashCode = 37 * hashCode + str.charCodeAt(i)
}

// 3.取模运算
hashCode = hashCode % max
return hashCode
}

// 插入数据方法
HashTable.prototype.put = function (key, value) {
// 1.获取key对应的index
var index = this.hashFunc(key, this.limit)

// 2.取出数组(也可以使用链表)
// 数组中放置数据的方式: [[ [k,v], [k,v], [k,v] ] , [ [k,v], [k,v] ] [ [k,v] ] ]
var bucket = this.storage[index]

// 3.判断这个数组是否存在
if (bucket === undefined) {
// 3.1创建桶
bucket = []
this.storage[index] = bucket
}

// 4.判断是新增还是修改原来的值.
var override = false
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i]
if (tuple[0] === key) {
tuple[1] = value
override = true
}
}

// 5.如果是新增, 前一步没有覆盖
if (!override) {
bucket.push([key, value])
this.count++

if (this.count > this.limit * 0.75) {
var primeNum = this.getPrime(this.limit * 2)
this.resize(primeNum)
}
}
}

// 获取存放的数据
HashTable.prototype.get = function (key) {
// 1.获取key对应的index
var index = this.hashFunc(key, this.limit)

// 2.获取对应的bucket
var bucket = this.storage[index]

// 3.如果bucket为null, 那么说明这个位置没有数据
if (bucket == null) {
return null
}

// 4.有bucket, 判断是否有对应的key
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i]
if (tuple[0] === key) {
return tuple[1]
}
}

// 5.没有找到, return null
return null
}

// 删除数据
HashTable.prototype.remove = function (key) {
// 1.获取key对应的index
var index = this.hashFunc(key, this.limit)

// 2.获取对应的bucket
var bucket = this.storage[index]

// 3.判断同是否为null, 为null则说明没有对应的数据
if (bucket == null) {
return null
}

// 4.遍历bucket, 寻找对应的数据
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i]
if (tuple[0] === key) {
bucket.splice(i, 1)
this.count--

// 缩小数组的容量
if (this.limit > 7 && this.count < this.limit * 0.25) {
var primeNum = this.getPrime(Math.floor(this.limit / 2))
this.resize(primeNum)
}
}
return tuple[1]
}

// 5.来到该位置, 说明没有对应的数据, 那么返回null
return null
}

// isEmpty方法
HashTable.prototype.isEmpty = function () {
return this.count == 0
}

// size方法
HashTable.prototype.size = function () {
return this.count
}

// 哈希表扩容
HashTable.prototype.resize = function (newLimit) {
// 1.保存旧的数组内容
var oldStorage = this.storage

// 2.重置属性
this.limit = newLimit
this.count = 0
this.storage = []

// 3.遍历旧数组中的所有数据项, 并且重新插入到哈希表中
oldStorage.forEach(function (bucket) {
// 1.bucket为null, 说明这里面没有数据
if (bucket == null) {
return
}

// 2.bucket中有数据, 那么将里面的数据重新哈希化插入
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i]
this.put(tuple[0], tuple[1])
}
}).bind(this)
}
}

评论