1.数组
1.线性数据结构强调存储与顺序
2.有东西先把东西存起来
数组特点
数组定长
数组是定长的,我们以为的其实是js引擎帮我们扩容,内存是操作系统来分配的
尽量给自己的东西设置好数组长度
特性
1.存储在物理空间上(在磁盘上)是连续的。如果存储的到头了,就再去磁盘里找,有没有一个空间能存放的下它。
2.底层数组的长度不变。(就是你一开始设置了是多长,就是多长)
3.数组变量指向第一个元素的位置(数组下标)
数组优点
1.查询性能好(通过偏移查询)
数组缺点
1.空间必须是连续的,数组比较大,当系统的 空间碎片较多的时候,容易存不下。
2.因为数组的长度是固定的,所以数组的内容难以被添加和删除,添加和删除是很消耗性能的。
在js中创建一个数组
let a = [1,2,3]
let arr = new Array(10) //确定数组长度
2.链表
数组的存放是连续的,链表的存放是可以碎片的,用引用来把他们连起来。
链表特点
1.空间上不连续
2.每存放一个值都会有一个引用空间
优点
1.只要内存足够大,就能存的下,不用担心空间碎片的问题
2.链表的添加和删除非常容易
缺点
1.链表的查询速度慢
2.链表的每一个查询都需要创建一个next值(就是引用值),直接double了空间。但是可以通过增加数据量的存储来降低引用值大小(引用值大小没变,只是相对于数据量变小了)
延伸(重要):
1.想传递一个链表,必须传递链表的根节点
2.每一个链表的节点都可以认为自己是根节点
数组和链表
循环遍历方法
数组
// 遍历数组 经典for循环
let a = [1, 2, 3, 4, 5]
function travel(array) {
if (array == null) return; // 一定要做这个检验
for (let index = 0; index < array.length; index++) {
console.log(array[index]);
}
}
travel(a)
链表
function ode(value) {
this.value = value;
this.next = null;
}
let a = new ode(1);
let b = new ode(2);
let c = new ode(3);
let d = new ode(4);
a.next = b;
b.next = c;
c.next = d;
// 循环遍历 传递一个链表 传的是根节点
function travel(root) {
let temp = root;
while (true) {
if (temp == null) {
break;
} else {
console.log(temp.value);
}
temp=temp.next;
}
}
travel(a)
递归遍历方法
数组
let a = [1, 2, 3, 4, 5]
function travel(arr, i) {
if (arr == null || i >= arr.length) return;
console.log(arr[i], '递归遍历');
travel(arr, i + 1)
}
travel(a, 0)
链表
function ode(value) {
this.value = value;
this.next = null;
}
let a = new ode(1);
let b = new ode(2);
let c = new ode(3);
let d = new ode(4);
a.next = b;
b.next = c;
c.next = d;
function travel(root) {
if (root == null) return;
console.log(root.value, '递归算法');
travel(root.next) // 找到一直在循环做的事情
}
travel(a)
链表逆置
function ode(value) {
this.value = value;
this.next = null;
}
let a = new ode(1);
let b = new ode(2);
let c = new ode(3);
let d = new ode(4);
a.next = b;
b.next = c;
c.next = d;
function reverse(root) {
//定位到倒数第二个节点,
if (root.next.next == null) {
//把最后一个节点指向倒数第二个
root.next.next = root;
//退出节点
return root.next;
} else {
//递归
let result = reverse(root.next);
//让该节点的下一个节点指向自己
root.next.next = root;
//让该节点指向null
root.next = null;
return result;
}
}
//遍历一下
function travel(root) {
if (root == null) return;
console.log(root.value);
travel(root.next)
}
travel(reverse(a))
冒泡排序(比较、交换、排序)
- 算法步骤
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
let arry = [4, 7, 2, 6, 0, 8, 9, 1, 3];
/**
- 比较
- 交换
- 排序
- */
function compare(a, b) {
if (a > b) return true;
else return false;
}
function exchange(arry, i, j) {
let temp = arry[j]
arry[j] = arry[i]
arry[i] = temp
}
function sort(arry) {
for (let i = 0; i < arry.length; i++) {
for (let j = 0; j < arry.length - 1 - i; j++) {
if (compare(arry[j], arry[j + 1])) {
exchange(arry, j, j + 1)
}
}
}
return arry;
}
console.log(sort(arry));
选择排序
- 算法步骤
- 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
- 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
- 重复第二步,直到所有元素均排序完毕。
let arry = [4, 7, 2, 6, 0, 8, 9, 1, 3];
function compare(a, b) {
if (a > b) return true;
else return false;
}
function exchange(arry, i, j) {
let temp = arry[j];
arry[j] = arry[i];
arry[i] = temp;
}
function sort(arry) {
for (let i = 0; i < arry.length; i++) {
let maxIndex = 0;
for (let j = 0; j < arry.length - i; j++) {
if (compare(arry[j], arry[maxIndex])) {
maxIndex = j;
}
exchange(arry, maxIndex, arry.length - 1 - i);
}
}
return arry;
}
console.log(sort(arry));
简单快速排序
- 算法步骤
- 从数列中挑出一个元素,称为 “基准”
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地把小于基准值元素的子数列和大于基准值元素的子数列排序;
let arr = [4, 7, 2, 6, 0, 8, 9, 1, 3];
function quicklySort(arr) {
if (arr.length == 0 || arr == null) return [];
let leader = arr[0];
let left = [];
let right = [];
for (let i = 1; i < arr.length; i++) {
if (arr[i] < leader) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
// 递归
left = quicklySort(left);
right = quicklySort(right);
// 把 left leader right 的内容给拼接起来
left.push(leader)
let newArry = left.concat(right)
return newArry
}
console.log(quicklySort(arr));
标准快速排序
let arr = [4, 7, 2, 6, 0, 8, 9, 1, 3];
//交换
function exchange(arr, a, b) {
let temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
// 值 ,左指针,右指针
function quicklySort(arr, begin, end) {
//数组的长度小于两个的时候就跳出
if (begin > end - 1) return;
let left = begin;
let right = end;
do {
do {
left++;
} while (left < right && arr[begin] > arr[left]);
do {
right--;
} while (left < right && arr[begin] < arr[right]);
if (left < right) exchange(arr, left, right);
} while (left < right);
let exchangePoint = left == right ? right - 1 : right;
exchange(arr, exchangePoint, begin);
quicklySort(arr, begin, exchangePoint);
quicklySort(arr, exchangePoint + 1, end)
}
function sort(arr) {
//左闭右开区间
quicklySort(arr, 0, arr.length);
}
sort(arr)
console.log(arr);
另一种方法 用到了 slice filter
let arr = [4, 7, 2, 6, 0, 8, 9, 1, 3];
/**
* 定义arr是一个number值的数组
* @param {number[]} arr
*/
function quicklySort(arr) {
if (arr.length < 2) {
return arr
}
let goal = arr[0]
//从数组的第一项开始,过滤出小于目标值的项
let minArr = arr.slice(1).filter(item => item <= goal)
//从数组的第一项开始,过滤出大于目标值的项
let maxArr = arr.slice(1).filter(item => item >= goal)
//递归 拼接
return quicklySort(minArr).concat([goal]).concat(quicklySort(maxArr))
}
console.log(quicklySort(arr));
3.栈
pop() 方法
用于删除数组的最后一个元素并返回删除的元素。
栈只有一个口
演示的代码
// 相当于 桶
let arr = [];
//进桶
function enter(arr, value) {
arr.push(value);
}
//出桶
function out(arr) {
arr.pop();
}
enter(arr,1)
console.log(arr);
enter(arr,2)
console.log(arr);
enter(arr,3)
console.log(arr);
out(arr)
console.log(arr);
out(arr)
console.log(arr);
输出:
简单封装一下代码:
function Stack() {
this.arr = [];
this.enter = function (value) {
return this.arr.push(value);
};
this.out = function () {
return this.arr.pop();
} ;
}
let stack = new Stack();
stack.enter(1)
console.log(stack.arr);
stack.enter(2)
console.log(stack.arr);
stack.enter(3)
console.log(stack.arr);
stack.out()
console.log(stack.arr);
stack.out()
console.log(stack.arr);
stack.out()
console.log(stack.arr);
4.队列
shift() 方法
用于把数组的第一个元素从其中删除,并返回第一个元素的值。
队列一个口进一个口出
function Queue() {
this.arr = [];
this.enter = function (value) {
return this.arr.push(value);
};
this.out = function () {
return this.arr.shift();
} ;
}
let queue = new Queue();
queue.enter(1)
console.log(queue.arr);
queue.enter(2)
console.log(queue.arr);
queue.enter(3)
console.log(queue.arr);
queue.out()
console.log(queue.arr);
queue.out()
console.log(queue.arr);
queue.out()
console.log(queue.arr);
输出
5.双向链表
function Node(value) {
this.value = value;
this.next = null;
this.pre = null;
}
let node1 = new Node(1)
let node2 = new Node(2)
let node3 = new Node(3)
node1.next = node2;
node2.pre = node1;
node2.next = node3;
node3.pre = node2;
优缺点
优点:能轻松的遍历整条链表
缺点:耗费存储空间
6.二维数据结构
树形结构(有向无环图)
特点
- 树形结构只有一个根节点
- 树形结构没有回路
- 叶子节点
- 下面没有根节点 - 节点
- 普通几点(不是根节点和叶子节点) - 树的度
-各个节点叉最多的,有多少叉 - 树的深度
- 树中结点的最大层次数
满二叉树
- 所有的叶子节点都在最底层
- 每个非叶子节点都有两个子节点
完全二叉树
- 国内定义:
1.叶子节点都在最后一或倒数第二层
2.叶子节点向左靠拢 - 国际定义:
1.叶子节点都在最后一或倒数第二层
2.如果有叶子节点,必然有两个叶子节点
遍历二叉树
前序遍历(先根次序遍历) 当前 左边 右边 ABDECFG
// 前序遍历(先根次序遍历) 当前 左边 右边 A BDE CFG
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
// 给每个节点赋值
let a = new Node("a");
let b = new Node("b");
let c = new Node("c");
let d = new Node("d");
let e = new Node("e");
let f = new Node("f");
let g = new Node("g");
//写左边邻居 右边邻居 A的左是B A的右是C
a.left = b;
a.right = c;
b.left = d;
b.right = e;
c.left = f;
c.right = g;
//遍历链表 树 之类的 要传递一个根节点进来 这里的根节点是a
function fl(root) {
if (root === null) return;
console.log(root.value);
// 小递归
fl(root.left)
fl(root.right)
}
fl(a);
打印结果
中序遍历(中根次序遍历) 左边 当前 右边 DBEAFCG
// 中序遍历(中根次序遍历) 左边 当前 右边 DBE A FCG
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
let a = new Node("a")
let b = new Node("b")
let c = new Node("c")
let d = new Node("d")
let e = new Node("e")
let f = new Node("f")
let g = new Node("g")
a.left = b;
a.right = c;
b.left = d;
b.right = e;
c.left = f;
c.right =g;
function fl(root) {
if(root === null) return ;
fl(root.left)
console.log(root.value);
fl(root.right)
}
fl(a)
运行结果
后序遍历(后根次序遍历) 左边 右边 当前 DEBFGCA
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
let a = new Node("a");
let b = new Node("b");
let c = new Node("c");
let d = new Node("d");
let e = new Node("e");
let f = new Node("f");
let g = new Node("g");
a.left = b;
a.right = c;
b.left = d;
b.right = e;
c.left = f;
c.right = g;
function fl(root) {
if (root === null) return;
fl(root.left);
fl(root.right);
console.log(root.value);
}
fl(a)
运行结果
还原二叉树
已知前序遍历和中序遍历还原二叉树 ,进行后序遍历
let forward = ["A", "B", "D", "E", "C", "F", "G",]
let middle = ["D", "B", "E", "A", "F", "C", "G",]
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
function f1(forward, middle) {
//严谨性判断
if (forward == null || middle == null || forward.length !== middle.length || forward.length == 0 || middle.length == 0) return null;
//前序遍历的第一个值就是根节点
let root = new Node(forward[0]);
//先找到 根节点 在middle里排第几位 3
let index = middle.indexOf(root.value);
//slice取的是左闭右开区间
//中序遍历的 左边 就是 在index 之前的 从下标第0个开始 到index个(不取index)
let middleLeft = middle.slice(0, index);
//中序遍历的 右边 就是 从index之后的
let middleRight = middle.slice(index + 1, middle.length)
//前序遍历的 左边 就是 从 index 之后的index+1个 因为index下标为0,所以从1开始
let forwardLeft = forward.slice(1, index + 1);
//前序遍历的 右边 就是 从 index+1个之后
let forwardRight = forward.slice(index + 1, forward.length)
//递归 先前序后中序
root.left = f1(forwardLeft, middleLeft)
root.right = f1(forwardRight, middleRight)
return root;
}
//后续遍历
function f2(root) {
if (root == null) return;
f2(root.left)
f2(root.right)
console.log(root.value);
}
f2(f1(forward, middle))
结果:
已知中序遍历和后序遍历还原二叉树 ,进行前序遍历
let middle = ["D", "B", "E", "A", "F", "C", "G",]
let back = ["D", "E", "B", "F", "G", "C", "A",]
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
function f1(middle, back) {
//严谨性判断
if (middle == null || back == null || back.length !== middle.length || back.length == 0 || middle.length == 0) return null;
//后续遍历的最后一个就是A
let root = new Node(back[back.length-1]);
//先找到 根节点 在middle里排第几位 3
let index = middle.indexOf(root.value);
//slice取的是左闭右开区间
//中序遍历的 左边 就是 在index 之前的
let middleLeft = middle.slice(0, index);
//中序遍历的 右边 就是 从index之后的
let middleRight = middle.slice(index + 1, middle.length)
//后序遍历的 左边 就是 从 1 之后的index+1个
let backLeft = back.slice(0, index );
//后序遍历的 右边 就是 从 index+1个之后
let backRight = back.slice(index , back.length-1)
//递归
root.left = f1(middleLeft, backLeft)
root.right = f1(middleRight, backRight)
return root;
}
function f2(root) {
if (root == null) return;
console.log(root.value);
f2(root.left)
f2(root.right)
}
f2(f1(middle,back))
运行结果
二叉树的深度优先搜索
优点:实现比较简单
缺点:容易爆栈,如果很多的话
function Node(value){
this.value = value;
this.left = null;
this.right = null;
}
let a = new Node("a");
let b = new Node("b");
let c = new Node("c");
let d = new Node("d");
let e = new Node("e");
let f = new Node("f");
let g = new Node("g");
a.left = b;
a.right = c;
b.left = d;
b.right = e;
c.left = f;
c.right = g;
function deepSearch(root,target){
if(root == null) return false;
if(root.value == target) return true;
let left = deepSearch(root.left,target);
let right = deepSearch(root.right,target);
return left || right
}
console.log(deepSearch(a,"g"));
假如说我搜索一个g,g在二叉树里面,所以结果是true
二叉树的广度优先搜索
实现方法:先拿个数组,把A装进去,如果没有,把A移除,装B,C,如果没有,把BC移除,装DEFG,如果找到了,返回true。
function Node(value){
this.value = value;
this.left = null;
this.right = null;
}
let a = new Node("a");
let b = new Node("b");
let c = new Node("c");
let d = new Node("d");
let e = new Node("e");
let f = new Node("f");
let g = new Node("g");
a.left = b;
a.right = c;
b.left = d;
b.right = e;
c.left = f;
c.right = g;
//广度优先搜索
function wideSearch(rootList,target){
if(rootList == null && rootList.length == 0) return false;
let childList = [];
for(let i = 0;i<rootList.length;i++){
if(rootList[i] !== null && rootList[i].value == target){
return true
}else{
childList.push(rootList[i].left)
childList.push(rootList[i].right)
}
}
//如果最后一位为空的话,就返回false
if(childList[childList.length-1]==null){
return false
}else{
return wideSearch(childList,target)
}
}
console.log(wideSearch([a],"h"));
假如说我搜索一个h,h不在二叉树里面,返回false
二叉树的比较
默认左右子树不分次序
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
let a1 = new Node("a");
let b1 = new Node("b");
let c1 = new Node("c");
let d1 = new Node("d");
let e1 = new Node("e");
let f1 = new Node("f");
let g1 = new Node("g");
a1.left = b1;
a1.right = c1;
b1.left = d1;
b1.right = e1;
c1.left = f1;
c1.right = g1;
let a2 = new Node("a");
let b2 = new Node("b");
let c2 = new Node("c");
let d2 = new Node("d");
let e2 = new Node("e");
let f2 = new Node("f");
let g2 = new Node("g");
a2.left = b2;
a2.right = c2;
b2.left = d2;
b2.right = e2;
c2.left = f2;
c2.right = g2;
function compareTree(root1,root2){
//都为空的话肯定是相同的
if(root1 == null && root2 == null) return true
//一个为空 另一个不为空的话,结构都不一样,肯定是不相同的
if(root1 == null && root2 !== null ||root1 !== null && root2 == null) return false
//value值不相等,两个二叉树不相同
if(root1.value!==root2.value) return false
let leftBool = compareTree(root1.left,root2.left)
let rightBool = compareTree(root2.right,root2.right)
return leftBool && rightBool
}
console.log(compareTree(a1,a2));
二叉树左右子树互换后的比较
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
let a1 = new Node("a");
let b1 = new Node("b");
let c1 = new Node("c");
let d1 = new Node("d");
let e1 = new Node("e");
let f1 = new Node("f");
let g1 = new Node("g");
a1.left = b1;
a1.right = c1;
b1.left = d1;
b1.right = e1;
c1.left = f1;
c1.right = g1;
let a2 = new Node("a");
let b2 = new Node("b");
let c2 = new Node("c");
let d2 = new Node("d");
let e2 = new Node("e");
let f2 = new Node("f");
let g2 = new Node("g");
a2.left = c2; //互换了c2,b2
a2.right = b2; //互换了c2,b2
b2.left = d2;
b2.right = e2;
c2.left = f2;
c2.right = g2;
function compareTree(root1, root2) {
if (root1 == null && root2 == null) return true
if (root1 == null && root2 !== null || root1 !== null && root2 == null) return false
if (root1.value !== root2.value) return false
return compareTree(root1.left, root2.left) && compareTree(root1.right, root2.right) || compareTree(root1.left, root2.right) && compareTree(root1.right, root2.left)
}
console.log(compareTree(a1, a2));
结果为true
二叉树的diff算法
输出二叉树不一样的地方
增加了什么,改动了什么,删除了什么
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
let a1 = new Node("a");
let b1 = new Node("b");
let c1 = new Node("c");
let d1 = new Node("d");
let e1 = new Node("e");
let f1 = new Node("f");
let g1 = new Node("g");
a1.left = b1;
a1.right = c1;
b1.left = d1;
b1.right = e1;
c1.left = f1;
c1.right = g1;
let a2 = new Node("a");
let b2 = new Node("b");
let c2 = new Node("c");
let d2 = new Node("d");
let e2 = new Node("e");
let f2 = new Node("f");
let g2 = new Node("g");
a2.left = b2;
a2.right = c2;
b2.left = d2;
b2.right = e2;
c2.left = f2;
c2.right = g2;
// let diffList=[
// {
// type:"增加了||删除了||改动了",
// origin:c1, //原始
// now: ff , //现在
// }
// ];
let diffList = [];
function diffTree(root1,root2,diffList){
if(root1 == root2) return diffList;
if(root1 == null && root2 != null){
diffList.push({
type:"增加了什么",
origin:null,
now:root2,
});
}else if( root1 != null && root2 == null){
diffList.push({
type:"删除了什么",
origin:root1,
now:null,
});
}else if(root1.value !=root2.value){
diffList.push({
type:"改动了",
origin:root1.value,
now:root2.value,
})
}else{
diffTree(root1.left,root2.left,diffList);
diffTree(root1.right,root2.right,diffList);
}
}
diffTree(a1,a2,diffList);
console.log(diffList);
7.图
普利姆算法(加点法)
1.挑选出一个节点
2.挑选出一个最短的边区进行联通
3.如果对方没有被联通进系统里则进行联通
4.如果被连进了系统里则去寻找第二短的边
5.重复2-4步
克鲁斯卡尔算法
1.先找最小的边
2.如果说链接的两个点有为两个系统的或者至少有一个没有被链接
3.则加边
4.重复以上三部