1.稀疏数组
稀疏数组 sparsearray 数组,把有值的位置都存下来 转换成行列更少的数组
二维数组转稀疏数组
let arr = [[0,0,1,2,0], [3,0,,4,0], [0,5,0,0,6], [7,0,8,0,0], [0,0,9,0,0]]
let sparse = []
for( let i = 0; i < arr.length; i++ ){
for( let j = 0; j < arr[i].length; j++ ){
if( arr[i][j] > 0 ){
sparse.push([i, j, arr[i][j]])
}
}}
sparse.unshift([arr.length, arr[0].length, sparse.length])
console.log(sparse);
稀疏数组转二维数组
let newArr = new Array( sparse[0][0] )
for( let i = 0; i < newArr.length; i++ ){
newArr[i] = new Array( sparse[0][1] )
}
for( let i = 1; i < sparse.length; i++){
newArr[sparse[i][0]][sparse[i][1]] = sparse[i][2]
}
console.log(newArr);
2.队列
有序列表,可以用数组实现
/ 有序列表,可以用数组或者链表实现
// 遵循先入先出的原则
function ArrayQueue( options ) {
let { maxLength } = options
this.maxLength = maxLength
this.front = -1
this.rear = -1
this.arr = []
}
ArrayQueue.prototype.isEmpty = function() {
return this.front == this.rear
}
ArrayQueue.prototype.isFull = function() {
return this.maxLength - 1 == this.rear
}
ArrayQueue.prototype.addQueue = function(item) {
if( this.isFull() ) {
return
}
this.rear++
this.arr[this.rear] = item
}
ArrayQueue.prototype.deQueue = function() {
if( this.isEmpty() ) {
return
}
this.front++
}
ArrayQueue.prototype.showQueue = function() {
let joinArr = []
this.arr.forEach( ( item, index)=> {
if( index > this.front && index <= this.rear ) {
joinArr.push( item )
}
})
return joinArr
}
let queue = new ArrayQueue({ maxLength: 5 })
queue.addQueue('a')
queue.addQueue('b')
queue.addQueue('c')
queue.addQueue('d')
queue.addQueue('e')
queue.addQueue('f')
3.环形队列
环形队列判满条件:(rear +1) % maxsize == front
环形队列判空条件:rear == front
环形队列有效数据的个数 (rear + maxsize - front) % maxsize
入队 arr[rear] = num rear = ( rear + 1 ) % maxsize 元素放入空间,rear后移一位
出队 e = arr[front] front = (front + 1 ) % maxsize front 后移一位
环形队列中rear的含义:指向队列中最后一个数据的前一个位置,因为要预留一个空间作为约定
环形队列中front的含义:指向队列头(即:队列中的第一个数据)
*/
function CircleQueue(k) {
this.maxsize = k
this.arr = new Array(k)
this.front = 0
this.rear = 0
}
// 判断队列满了
CircleQueue.prototype.isFull = function() {
return ( this.rear + 1 ) % this.maxsize == this.front
}
// 判断队列为空
CircleQueue.prototype.isEmpty = function() {
return this.front == this.rear
}
// 入队
CircleQueue.prototype.enqueue = function( n ) {
if( this.isFull() ) {
console.log('队列满了');
return
}
this.arr[this.rear] = n
this.rear = ( this.rear + 1 ) % this.maxsize
}
// 出队
CircleQueue.prototype.dequeue = function() {
if( this.isEmpty() ) {
console.log('队列空了');
return
}
let value = this.arr[this.front]
this.arr[this.front] = ''
this.front = ( this.front + 1 ) % this.maxsize
return value
}
// 展示队列
CircleQueue.prototype.showQueue = function() {
console.log(this.arr);
// let len = ( this.rear - this.front + this.maxsize ) % this.maxsize
// for( let i = this.front; i < len ; i ++ ) {
// console.log( this.arr[ i % this.maxsize ] )
// }
}
let cQue = new CircleQueue(5)
cQue.enqueue('a')
cQue.enqueue('b')
cQue.enqueue('c')
cQue.enqueue('d')
cQue.enqueue('e')
cQue.dequeue()
cQue.dequeue()
cQue.enqueue('1')
cQue.enqueue('2')
cQue.enqueue('3')
cQue.showQueue()
4.单向链表
function Node( data ) {
this.data = data
this.next = null
}
function LinkList() {
this.head = new Node('head')
}
LinkList.prototype.find = function( item ) {
let currentNode = this.head
while( currentNode && currentNode.data != item ) {
currentNode = currentNode.next
}
return currentNode
}
LinkList.prototype.findPrev = function( item ) {
let currentNode = this.head
while( (currentNode.next && currentNode.next.data != item) ) {
currentNode = currentNode.next
}
return currentNode
}
LinkList.prototype.insert = function( newEle, item ) {
let newNode = new Node( newEle )
let currentNode = this.find( item )
newNode.next = currentNode.next
currentNode.next = newNode
}
LinkList.prototype.remove = function( item ) {
let nodePrev = this.findPrev( item )
nodePrev.next = nodePrev.next.next
}
LinkList.prototype.show = function() {
let current = this.head
while( !(current.next == null) ) {
console.log(current.data);
current = current.next
}
console.log(current.data);
}
let lList = new LinkList()
lList.insert('a', 'head')
lList.insert('b', 'a')
lList.insert('c', 'b')
lList.insert('d', 'c')
lList.remove('c')
lList.show()
5.双向链表
function Node( data ) {
this.data = data
this.prev = null
this.next = null
}
function LinkList() {
this.head = new Node('head')
}
LinkList.prototype.find = function( item ) {
let currentNode = this.head
while( currentNode && (currentNode.data != item) ) {
currentNode = currentNode.next
}
return currentNode
}
LinkList.prototype.insert = function( newitem, item ) {
let newNode = new Node( newitem )
let currentNode = this.find( item )
newNode.next = currentNode.next
newNode.prev = currentNode
currentNode.next = newNode
}
LinkList.prototype.show = function() {
let currentNode = this.head
while( currentNode.next != null ) {
console.log( currentNode.data )
currentNode = currentNode.next
}
console.log( currentNode.data )
}
LinkList.prototype.remove = function( item ) {
let currentNode = this.find( item )
if( currentNode && currentNode.next && currentNode.prev ) {
currentNode.prev.next = currentNode.next
currentNode.next.prev = currentNode.prev
currentNode.next = null
currentNode.prev = null
}
}
let lList = new LinkList()
lList.insert('a', 'head')
lList.insert('b', 'a')
lList.insert('c', 'b')
lList.show()
lList.remove('a')
lList.show()
6.单向环形链表
function Node( data ) {
this.data = data
this.next = null
}
function CirNodeList() {
this.head = null
this.length = 0
}
CirNodeList.prototype.find = function( position ) {
let currentNode = this.head
for( let i = 0; i < position; i++) {
currentNode = currentNode.next
}
return currentNode
}
CirNodeList.prototype.insert = function( position, newData ) {
if( position < 0 || position >= this.length ) {
return false
}
let newItem = new Node( newData )
if( position == 0 && this.head == null ) {
this.head = newItem
newItem.next = this.head
}else{
let currentNode = this.find(position)
newItem.next = currentNode.next
currentNode.next = newItem
}
this.length++
}
CirNodeList.prototype.append = function( data ) {
let newItem = new Node( data )
if( this.length == 0 ) {
this.head = newItem
newItem.next = this.head
}else{
let currentNode = this.findLast()
newItem.next = this.head
currentNode.next = newItem
}
this.length++
}
CirNodeList.prototype.findLast = function() {
let currentNode = this.head
for( let i = 0; i < this.length - 1 ; i++ ) {
currentNode = currentNode.next
}
return currentNode
}
CirNodeList.prototype.show = function() {
let currentNode = this.head
for( let i = 0; i < this.length ; i++ ) {
console.log(currentNode.data);
currentNode = currentNode.next
}
}
CirNodeList.prototype.remove = function( position ) {
if( position < 0 || position > this.length ) {
return null
}
if( position == 0 ) {
let currentNode = this.head
this.head = currentNode.next
}else{
let prevNode = this.find( position - 1 )
let currentNode = prevNode.next
prevNode.next = currentNode.next
}
this.length--
}
let cirNodeList = new CirNodeList()
cirNodeList.append('head')
cirNodeList.append('a')
cirNodeList.append('b')
// cirNodeList.insert(0,'head')
// cirNodeList.insert(1,'a')
cirNodeList.insert(1,'1')
cirNodeList.remove( 2 )
cirNodeList.show()
6.1 约瑟夫问题
问题的来历:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,
39个犹太人决定宁愿死也不要被敌人抓。于是决定了自杀方式,
41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀。
然后下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从,
Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏
7.栈
function ArrayStack(k) {
this.maxsize = k
this.top = -1
this.stack = []
}
ArrayStack.prototype.isEmpty = function() {
return this.top == -1
}
ArrayStack.prototype.isFull = function() {
return this.top == this.maxsize - 1
}
ArrayStack.prototype.push = function( item ) {
if( this.isFull() ) {
return
}
this.stack.push(item)
this.top++
}
ArrayStack.prototype.pop = function() {
if( this.isEmpty() ) {
return
}
let value = this.stack.pop()
this.top--
return value
}
ArrayStack.prototype.show = function() {
if( this.isEmpty() ) {
return
}
for( let i = this.top; i >= 0; i--) {
console.log(this.stack[i]);
}
}
ArrayStack.prototype.peek = function() {
return this.stack[this.top - 1]
}
// let arrayStack = new ArrayStack(4)
// arrayStack.push(1)
// arrayStack.push(2)
// arrayStack.push(3)
// arrayStack.push(4)
// arrayStack.show()
7.1 中缀表达式转前缀表达式
/*思路 初始化两个栈:运算符栈 S1; 操作数栈 S2 1从右至左扫描中缀表达式 2.遇到操作数时,将其压入 S2 3.遇到运算符时,比较其与 S1 栈顶运算符的优先级 如果 S1 为空,或栈顶运算符为右括号 ")",或其优先级比栈顶运算符的优先级较高或相等,则直接将此运算符入栈 否则,将 S1 栈顶的运算符弹出并压入到 S2 中,再次进行与 S1 栈顶运算符的优先级比较 4.遇到括号时 如果是右括号 ")",则直接压入 S1 如果是左括号 "(",则依次弹出 S1 栈顶的运算符,并压入 S2,直到遇到右括号 ")" 为止,此时将这一对括号丢弃 重复步骤 2 至 5,直到表达式的最左边 5.将 S1 剩余的运算符依次弹出并压入 S2 6.依次弹出 S2 中的元素并输出,结果即为中缀表达式对应的前缀表达式*/ (1 + (3 * 4) / 6 ) - 5 ==> -+1/*3465
let str = '(1+(30*4)/6)-5'
function prefixExpression( str ) {
let matchReg = /\d+|\+|\-|\*|\/|\(|\)/g
let arr = str.match(matchReg)
let numReg = /^\d+$/
let operReg = /^\+|\-|\*|\/$/
let numStack = [], operStack = []
for( let i = arr.length - 1; i > -1; i-- ) {
// 数字
if( numReg.test(arr[i]) ) {
numStack.push( arr[i] )
}
// 操作符
else if( operReg.test( arr[i] ) ) {
while(
!(
operStack.length == 0 ||
operStack[ operStack.length - 1 ] == ')' ||
getOperatorLevel( arr[i] ) >= getOperatorLevel(operStack[operStack.length - 1])
)
) {
numStack.push(operStack.pop())
}
operStack.push(arr[i])
}
// (
else if( arr[i] == '(' ) {
let popItem
while( ( popItem = operStack.pop() ) != ')') {
numStack.push(popItem)
}
}
//
else if( arr[i] == ')' ) {
operStack.push(arr[i])
}
}
while( operStack.length > 0 ) {
numStack.push(operStack.pop())
}
let express = ''
while( numStack.length ) {
express += numStack.pop()
}
return express
}
function getOperatorLevel( oper ) {
let LOW_OPER_REG = /\+|\-/
let HIGH_PPER_REG = /\*|\//
if( LOW_OPER_REG.test(oper) ) {
return 1
}else if( HIGH_PPER_REG.test(oper) ) {
return 2
}
}
7.2 中缀表达式转后缀
/*
中缀表达式转后缀步骤
初始化两个栈:运算符栈 S1; 操作数栈 S2
从左至右扫描中缀表达式
1.遇到操作数时,将其压入 S2
2.遇到运算符时,比较其与 S1 栈顶运算符的优先级
如果 S1 为空,或栈顶运算符为左括号 "(",或其优先级比栈顶运算符的优先级较高,则直接将此运算符入栈
否则,将 S1 栈顶的运算符弹出并压入到 S2 中,再次进行与 S1 栈顶运算符的优先级比较
3.遇到括号时
如果是左括号 "(",则直接压入 S1
如果是右括号 ")",则依次弹出 S1 栈顶的运算符,并压入 S2,直到遇到左括号 "(" 为止,此时将这一对括号丢弃
4.重复步骤 2 至 5,直到表达式的最右边
5.将 S1 剩余的运算符依次弹出并压入 S2
6.拼接 S2 中的元素并输出,结果即为中缀表达式对应的后缀表达式
(1 + (3 * 4) / 6 ) - 5 ==> 134*6/+5-
*/
function getSuffixExpression( str ) {
let matchReg = /\d+|\+|\-|\*|\/|\(|\)/g
let arr = str.match(matchReg)
let numReg = /^\d+$/
let operReg = /^\+|\-|\*|\/$/
let numStack = [], operStack = []
for( let i = 0; i < arr.length; i++ ) {
let cur = arr[i]
if( numReg.test(cur) ) {
numStack.push( cur )
}else if( operReg.test( cur ) ){
while(
!(
!operStack.length ||
operStack[operStack.length - 1] == '(' ||
getOperatorLevel( cur ) > getOperatorLevel(operStack[operStack.length - 1])
)
) {
numStack.push( operStack.pop() )
}
operStack.push( cur )
}else if( cur == '(' ) {
operStack.push( cur )
}else if( cur == ')' ) {
let popItem = null
while( (popItem = operStack.pop() ) != '(') {
numStack.push(popItem)
}
}
}
while( operStack.length ) {
numStack.push( operStack.pop() )
}
return numStack
}
7.3 计算后缀表达式
/*
思路
1.从左到右一次遍历数组
2.遇到数字直接推入栈,遇到运算符取出栈顶两个数组运算后把运算结果压入栈
3.遍历之后取出栈顶的元素就是运算结果
*/
function solution( arr ) {
let numReg = /^\d+$/
let stack = []
let num1, num2
while( arr.length ) {
let cur = arr.shift()
if( numReg.test(cur) ) {
stack.push( Number(cur) )
}else{
num1 = Number(stack.pop())
num2 = Number(stack.pop())
stack.push( cal( num1, num2, cur ) )
}
}
return stack.pop()
}
8.递归算法
// 用数学代入法理解f(n)的阶乘
fn = n * f(n-1) = n * n - 1 * f( n- 2 ) .... f(1)
先递进,再回归——这就是递归
function fn( n ) {
if(n <= 1 ) {
return n
}
return fn(n-1) * n
}
9.排序算法
9.1 冒泡排序
通过对待排序序列从前向后,依次比较相邻元素的值,
若发现逆序则交换,使值较大的元素逐渐从前移动到后部,就像水底下气泡一样逐渐向上冒
function bubbleSort( arr ) {
var temp
var flag = false // 用来判断某一次循环里面是否有过交换,如果没有交换证明排序已经是正确的了
for( let j = 0; j < arr.length -1 ; j++ ) {
for( let i = 0; i < arr.length - 1 - j; i++ ) {
if( arr[i] > arr[ i+1 ] ) {
flag = true
temp = arr[i]
arr[i] = arr[i+1]
arr[i+1] = temp
}
}
// 当一次循环中没有进行任何交换,就代表排序已经完成了,直接退出循环
if( !flag ) {
break
}else{
flag = false
}
}
}
9.2 选择排序
首先选择一个位置,这个位置的数据跟之后的数据比较选择最小的值,
然后选择下一个位置,同样比较出最小值,直到最后一个元素
function selectSort( arr ) {
for( let j = 0; j < arr.length - 1; j++ ) {
//首先选择出第一个位置,认为当前最小
min = arr[j]
minIndex = j
for( let i = j + 1; i < arr.length; i++ ) {
// 如果当前最小值比当前值大就交换数据,最终选出整个数组最小值
if( min > arr[i]) {
minIndex = i
min = arr[i]
}
}
if( minIndex != j ) {
arr[minIndex] = arr[j]
arr[j] = min
}
}
}
9.3 插入排序
把n个待排序的元素看成为一个有序表和一个无序表,开始时候有序表只有一个元素,无序表中包含n-1个元素排序过程中每次从无序表中取出第一个元素把他的排序码依次与有序的排序码进行比较,
将它插入到有序表中适当的位置,使之成为新的有序表
function insertSort(arr) {
let len = arr.length
let preIndex, current
for( let i = 1; i < len; i++ ) {
preIndex = i - 1
current = arr[i]
while( preIndex >= 0 && current < arr[preIndex] ) {
arr[preIndex + 1] = arr[preIndex]
preIndex--
}
if( preIndex + 1 != i ) {
arr[preIndex+1] = current
}
}
}
9.4 希尔排序
也叫缩小增量排序 交换法 先定义一个 gap 然后将 gap 的元素分为一组,
对每个组进行插入排序,然后不断缩短 gap, 直到 gap 为 1完成全部排序
function shellSort( arr ) {
let len = arr.length
// console.log(len / 2);
for( let gap = Math.floor( len / 2 ) ; gap > 0; gap = Math.floor( gap / 2 ) ) {
// console.log(gap );
for( let i = gap; i < len; i++ ) {
let j = i
let temp = arr[j]
if( arr[j] < arr[j - gap] ) {
while( j - gap >= 0 && temp < arr[j - gap]) {
// 移动
arr[j] = arr[j-gap]
j -= gap
}
// 当退出while 循环后找到了插入位置,
arr[j] = temp
}
}
}
}
9.5 快速排序
是对冒泡排序的一种改进, 基本思想是,通过一趟排序将要排序的数据分割成独立的两部分其中一部分的所有数据比另外一部分的所有数据都要小,
然后再按此方法对这两部分数据分别进行快速排序个排序过程可以递归进行,从此达到整个数据变成有序序列
function quickSort( arr, start, end ) {
let len = arr.length
if( len <= 1 ) {
return arr
}
let l = start, r = end;
let pivotIndex = Math.floor( ( start + end ) / 2 )
let pivot = arr[pivotIndex]
let temp = 0
// while 的目的是为了让 比pivot小的值放到最左边,比 pivot 放到右边
while( l < r ) {
while( arr[l] < pivot) {
l += 1
}
while( arr[r] > pivot ) {
r -= 1
}
// 说明 pivot 左侧已经比他小,右侧的值比他大
if( l >= r ) {
break
}
// 交换
temp = arr[l]
arr[l] = arr[r]
arr[r] = temp
// 如果交换完成后, 如果 arr[l] == pivot r--, 前移一个
if( arr[l] == pivot ) {
r -= 1
}
// 如果交换完成后, 如果 arr[r] == pivot l--, 后移一个
if( arr[r] == pivot ) {
l += 1
}
}
// 如果 l == r 必须 l++ , r--, 否则会栈溢出
if( l == r ) {
l += 1
r -= 1
}
// 向左递归
if( start < r ) {
quickSort( arr, start, r )
}
// 向右递归
if( end > l ) {
quickSort( arr, l, end )
}
}
9.6 归并排序
是利用归并的思想实现的排序方法,该算法采用经典的分治策略分治法将问题 分 为一些小的问题然后递归求解,
而 治 的阶段将分的阶段得到的答案 修补在一起,分而治之)
// 先将数组 递归左分解和右分解, 之后将分解好的数组进行 排序
function mergeSort( arr, left, right, temp ) {
if( left < right ) {
let mid = Math.floor( ( left + right ) / 2 )
// 向左递归分解
mergeSort( arr, left, mid, temp )
// 向右递归分解
mergeSort( arr, mid + 1, right, temp )
merge( arr, left, mid, right, temp )
}
}
function merge( arr, left, mid, right, temp ) {
let l = left // 初始化左边索引
let r = mid + 1 // 初始化右边索引
let t = 0 // 临时存储的索引
// 1.
// 先把左右两边(有序)的数据按照规则填充到temp 数组
// 直到左右两边的有序序列,有一边处理完毕为止
while( l <= mid && r <= right) {
// 如果左边的有序序列的当前元素,小于等于右边有序的当前元素
// 将左边的当前元素,拷贝到temp数组
// 然后 t 后移, l 也后移
if( arr[l] <= arr[r] ) {
temp[t] = arr[l]
t += 1
l += 1
}else{ // 反之,将右边有序序列的当前元素,填充到temp 数组中
temp[t] = arr[r]
t += 1
r += 1
}
}
// 2.
// 把有剩余数据的一边的数据,依次全部填充到temp
while( l <= mid ) { // 说明左边的有序序列还有剩余的元素,全部填充到temp
temp[t] = arr[l]
t += 1
l += 1
}
while( r <= right ) {
temp[t] = arr[r]
t += 1
r += 1
}
// 3.
// 将temp数组的元素拷贝到arr
t = 0
let tempLeft = left
while( tempLeft <= right ) {
arr[tempLeft] = temp[t]
t += 1
tempLeft += 1
}
}
9.7 基数排序
桶排序, 通过键值的各个位的值,将要排序的元素分配至某些桶中,达到排序的作用,耗费额外的内存空间
function radixSort( arr ) {
// 第一轮 针对每个元素 的个位进行排序处理
// 定义一个二维数组, 表示10个桶, 每个桶就是一个一维数组
let bucket = []
for( let i = 0; i < 10; i++ ) {
bucket.push( new Array(arr.length) )
}
let bucketEleCount = new Array(10).fill(0)
// 得到数组中最大数的位数
let max = arr[0] // 假设最大数是最大数
for( let i = 1; i < arr.length; i++ ) {
if( arr[i] > max ) {
max = arr[i]
}
}
// 得到最大数的长度是几位数
let maxLength = ( max + '').length
for( let i = 0, n = 1; i < maxLength; i++, n = n * 10 ) {
for( let j = 0; j < arr.length; j++ ) {
let digitOfElement = Math.floor( arr[j] / n ) % 10
bucket[digitOfElement][bucketEleCount[digitOfElement]] = arr[j]
bucketEleCount[digitOfElement]++
}
let index = 0
for( let k = 0; k < bucket.length; k++ ) {
if( bucketEleCount[k] != 0 ) {
for( let l = 0; l < bucketEleCount[k]; l++ ) {
arr[index] = bucket[k][l]
index++
}
bucketEleCount[k] = 0
}
}
}
}
时间和空间复杂度
平方阶 (O(n2)) 排序 各类简单排序:直接插入、直接选择和冒泡排序。
线性对数阶 (O(nlog2n)) 排序 快速排序、堆排序和归并排序;
O(n1+§)) 排序,§ 是介于 0 和 1 之间的常数。 希尔排序
线性阶 (O(n)) 排序 基数排序,此外还有桶、箱排序。
关于稳定性
稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序。
不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。
名词解释:
- n:数据规模
- k:"桶"的个数
- In-place:占用常数内存,不占用额外内存
- Out-place:占用额外内存
- 稳定性:排序后 2 个相等键值的顺序和排序之前它们的顺序相同