排序
1.冒泡排序
第一层循环为大循环,
第二层循环为小循环,两两对比,每次对比完后,将最大值送至最后一位。
比较的次数为
复杂度(保留最高阶并去掉常数项)为
function bubbleSort(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
let temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr
}
let arr1 = [6, 5, 4, 3, 2, 1];
console.log(bubbleSort(arr1));
2.选择排序
第一层为大循环,每次循环时将第一位索引赋值给min,
第二层循环为小循环,(打擂台的形式)将第一个的索引然后将第一个数跟其余的相比较,每次相比如果有比第一个数小的值就将其索引放入min,比完第一轮再将该数arr[min]与第一个值交换,然后又从第二个值开始循环往复。
比较次数
交换次数
function selectSort(arr) {
for (let i = 0; i < arr.length - 1; i++) {
let min = i;
for (let j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[min]) {
min = j; //序号
}
}
if (min != i) {
let temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}
return arr;
}
let arr1 = [2, 4, 3, 5, 1];
console.log(selectSort(arr1));
3.插入排序
第一层循环为大循环,默认第0位为有序,将第1位复制给temp,每次循环都将i>0赋值给temp
第二层循环为小循环,每次将temp与前面的值arr[j-1]比较,然后j--,循环比较,若temp小于该值,就一个把temp移到该值前面,如何移动?每一次小循环,将前一个大于后面的数,直接赋值给后一个数,比如5 4 3 就变成 5 5 4,然后temp是3,最后将它赋值给第j(j--)位。
比较次数(平均来说比选择排序少了一半,但是算最高次幂还是)
function insertSort(arr) {
for (let i = 1; i < arr.length; i++) {
let temp = arr[i];
let j;
for (j = i; temp < arr[j - 1] && j > 0; j--) {
arr[j] = arr[j - 1];
}
arr[j] = temp;
}
return arr;
}
let arr1 = [2, 4, 3, 5, 1];
console.log(insertSort(arr1));
4.希尔排序
原理与插入排序相似,但是插入排序是假定前面有序,然后往这个序列一个一个插入;而希尔排序是分成几个小组,对每个小组来进行插入排序
时间复杂度最坏的情况为,但通常要好于
function insertSort(arr) {
let gap = Math.floor(arr.length / 2);
while (gap !== 0) {
for (let i = gap; i < arr.length; i++) {
let temp = arr[i];
let j;
for (j = i; temp < arr[j - gap] && j > gap - 1; j -= gap) {
arr[j] = arr[j - gap];
}
arr[j] = temp;
}
gap = Math.floor(gap / 2);
}
return arr;
}
let arr1 = [2, 4, 3, 5, 1];
console.log(insertSort(arr1));
5.快速排序
这里直接将第一个值为基准数,然后i再左,往右走,找到大于等于基准数的就停;j在右,往左走,找到小于基准数的就停,然后分别交换值
然后就这样循环找和交换,直到不满足i < j为止
然后将基准数和中间那个找到的数交换
这样就可以得到基准数左边全是小于它的数,右边全是大于它的数
然后分而治之,左边递归,右边递归
function quick_sort(arr, from, to) {
var i = from; //哨兵i
var j = to; //哨兵j
var key = arr[from]; //标准值
if (from >= to) { //如果数组只有一个元素
return;
}
while (i < j) {
while (arr[j] > key && i < j) { //从右边向左找第一个比key小的数,找到或者两个哨兵相碰,跳出循环
j--;
}
while (arr[i] <= key && i < j) { //从左边向右找第一个比key大的数,找到或者两个哨兵相碰,跳出循环
i++;
}
// /**
// 代码执行道这里,1、两个哨兵到找到了目标值。2、j哨兵找到了目标值。3、两个哨兵都没找到(key是当前数组最小值)
// **/
if (i < j) { //交换两个元素的位置
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
arr[from] = arr[i] //
arr[i] = key;
quick_sort(arr, from, i - 1);
quick_sort(arr, i + 1, to);
}
var arr = [3, 3, -5, 6, 0, 2, -1, -1, 3];
quick_sort(arr, 0, arr.length - 1);
console.log(arr);
链表
指针介绍
在放代码之前首先介绍一下之后要用到的相关指针具体含义比如this.head,newnode等。
其中,指针也就是内存地址,指针变量是用来存放内存地址的变量。
1)newnode:newnode也是一个指针,所指向的节点node里面有data和next,next本身也是个指针变量,里面存放下一个node的地址
2)newnode.next:newnode就是newnode本身这个指针,而newnode.表示要去newnode这个内存区域里找它真正存放的那个节点,newnode指向这个节点,所以newnode.next表示这个节点的node的next;
3)this.head: 这是头节点,也是一个指针,所以this.head=newnode相当于把指针newnode的地址赋值给了this.head,所以this.head指向这个newnode;
4)this.head.next: this.head若指向第一个节点node,则this.head.next就是第一个节点node的next;
5)newnode = this.head:由于this.head存了第一个节点的地址,即指向第一个节点,现把该地址赋值给newnode,相当于指newnode也指向了该节点(见下图蓝箭头);
6)newnode.next = this.head:指针newnode指向的节点的next指向了这个节点,这也是写链表各类方法所经常用到的(见下图红箭头)。
上代码~
这里采用es5的写法,利用构造函数来创建对象,并且还需要在里面再定义一个内部的类即节点类来描述有data和next的链表数据结构
function LinkedList() {
// 内部的类:节点类
function Node(data) {
this.data = data
this.next = null
}
// 属性
this.head = null // 默认情况指向null
this.length = 0 // 记录链表的长度
// 方法
LinkedList.prototype.append = function() {
// ``` 见下面
}
LinkedList.prototype.toString = function() {
// ``` 见下面
}
LinkedList.prototype.insert = function() {
// ``` 见下面
}
LinkedList.prototype.removeAt = function() {
// ``` 见下面
}
// ···等方法
}
append方法
LinkedList.prototype.append = function(data) {
let newNode = new Node(data);
if (this.length == 0) {
this.head = newNode; // 头节点指向新节点
} else {
let current = this.head;
// this.head指向第一个节点,this.head存的地址赋值给了current,current也就指向第一个节点
while (current.next) {
current = current.next;
// 假如current存的是第一个节点的地址,current.next存了下一个节点的地址,赋给current,此时current存的就是下一个节点的地址,current就指向了第二个节点。
}
current.next = newNode;
// 此时current已指向最后一个节点,current.next指向新节点即可
}
this.length++;
}
toString方法
LinkedList.prototype.toString = function() {
let current = this.head;
let linkStr = '';
while (current) {
linkStr += current.data + ' ';
current = current.next;
}
return linkStr;
}
insert方法
LinkedList.prototype.insert = function(position, data) {
let newNode = new Node(data)
if (position < 0 || position > this.length)
return false;
if (position == 0) {
newNode.next = this.head;
// this.head存的是第一个节点的地址,赋值给newNode.next,所以newNode.next存的也是第一个节点的地址,所以新节点的next指向第一个节点
this.head = newNode;
// 新节点地址赋值给头节点,所以头节点指向新节点
} else {
let cur = this.head;
let prev = null;
for (let i = 0; i < position; i++) {
prev = cur; //第一个点
cur = cur.next; //第二个点
}
newNode.next = cur;
prev.next = newNode;
}
this.length++;
return true
}
removeAt方法
LinkedList.prototype.removeAt = function(position) {
if (position < 0 || position >= this.length) return false;
if (position == 0) {
this.head = this.head.next;
} else {
let cur = this.head;
let prev = null;
for (let i = 0; i < position; i++) {
prev = cur;
cur = cur.next;
}
prev.next = cur.next;
}
this.length--;
}
完整代码
function LinkedList() {
function Node(data) {
// 用node类来表示链表这种数据结构
this.data = data;
this.next = null;
}
this.length = 0;
this.head = null;
LinkedList.prototype.append = function(data) {
<!--采用prototype来共享方法,节省内存-->
let newNode = new Node(data);
if (this.length == 0) {
this.head = newNode;
} else {
let current = this.head;
while (current.next) {
current = current.next;
}
current.next = newNode;
}
this.length++;
}
LinkedList.prototype.toString = function() {
let current = this.head;
let linkStr = '';
while (current) {
linkStr += current.data + ' ';
current = current.next;
}
return linkStr;
}
LinkedList.prototype.insert = function(position, data) {
let newNode = new Node(data)
if (position < 0 || position > this.length)
return false;
if (position == 0) {
newNode.next = this.head;
this.head = newNode;
} else {
let cur = this.head;
let prev = null;
for (let i = 0; i < position; i++) {
prev = cur; //第一个点
cur = cur.next; //第二个点
}
newNode.next = cur;
prev.next = newNode;
}
this.length++;
return true
}
LinkedList.prototype.removeAt = function(position) {
if (position < 0 || position >= this.length) return false;
if (position == 0) {
this.head = this.head.next;
} else {
let cur = this.head;
let prev = null;
for (let i = 0; i < position; i++) {
prev = cur;
cur = cur.next;
}
prev.next = cur.next;
}
this.length--;
}
}
let linkList = new LinkedList();
linkList.append('111')
linkList.append('222')
linkList.append('333')
linkList.insert(3, '444')
linkList.removeAt(2)
console.log(linkList.toString());
二叉树
一个二叉树第 层的最大节点数:
深度为 的二叉树有最大节点总数:
1. 完美二叉树
高度为 ,并且由
个结点组成的二叉树,称为完美二叉树。
2. 完全二叉树
一棵二叉树中,只有最下面两层结点的度可以小于2,并且最下层的叶结点集中在靠左的若干位置上,这样的二叉树称为完全二叉树。
3. 二叉搜索树
相关代码:
function Node(data) {
this.data = data;
this.left = null;
this.right = null;
}
function BST() {
this.root = null;
}
// 方法
BST.prototype.insert = function() {
// ``` 见下面
}
BST.prototype.preOrder = function() {
// ``` 见下面
}
BST.prototype.midOrder = function() {
// ``` 见下面
}
BST.prototype.postOrder = function() {
// ``` 见下面
}
// ···等方法
insert方法
BST.prototype.insert = function(data) {
let newNode = new Node(data);
if (this.root == null) {
this.root = newNode;
} else {
this.insertNode(this.root, newNode);
}
}
BST.prototype.insertNode = function(node, newNode) {
if (newNode.data < node.data) {
if (node.left != null) {
this.insertNode(node.left, newNode);
} else {
node.left = newNode;
}
} else {
if (node.right != null) {
this.insertNode(node.right, newNode)
} else {
node.right = newNode;
}
}
}
先序遍历
左节点相关代码还没执行就打印
BST.prototype.preOrder = function(handler) {
this.preOrderTraversalNode(this.root, handler)
}
BST.prototype.preOrderTraversalNode = function(node, handler) {
if (node) {
handler(node.data);
this.preOrderTraversalNode(node.left, handler);
this.preOrderTraversalNode(node.right, handler);
}
}
// 测试数据
var bst = new BST();
var nums = [5, 3, 8, 1, 4, 7, 9];
for (var i = 0; i < nums.length; i++) {
bst.insert(nums[i]);
}
let str = '';
bst.preOrder(res => {
str += res + ' '
})
console.log(str); // 5 3 1 4 8 7 9
中序遍历
左节点相关代码执行完了就打印
BST.prototype.midOrder = function(handler) {
this.midOrderTraversalNode(this.root, handler)
}
BST.prototype.midOrderTraversalNode = function(node, handler) {
if (node) {
this.midOrderTraversalNode(node.left, handler);
handler(node.data);
this.midOrderTraversalNode(node.right, handler);
}
}
bst.midOrder(res => {
str += res + ' '
})
console.log(str); // 1 3 4 5 7 8 9 相当于按顺序
后续遍历
左节点右节点相关代码执行完了再打印
BST.prototype.postOrder = function(handler) {
this.postOrderTraversalNode(this.root, handler);
}
BST.prototype.postOrderTraversalNode = function(node, handler) {
if (node) {
this.postOrderTraversalNode(node.left, handler);
this.postOrderTraversalNode(node.right, handler);
handler(node.data);
}
}
bst.postOrder(res => {
str += res + ' '
})
console.log(str); // 1 4 3 7 9 8 5
面试其他题
笔试
小明去买口罩,A类2个2元,B类3个2元,C类1个3元,D类5个1元,E类4个5元,F类3个2元,每类口罩限购一个,问小明最多能买多少个口罩?
输入:9
输出:13
这本质上是01背包问题
采用动态规划