- 程序=数据结构+算法
- 数据结构包括了线性结构和非线性结构,线性结构通常是1对1的,又分为顺序存储结构和链式存储结构,常见的线性数据结构有数组、队列、链表和栈;非线性结构与线性结构相对应,不是一对一的,常见的非线性结构有二维数组、多维数组、广义表、树结构和图结构
稀疏数组(sparsearray)
- 引入:五子棋棋盘的存档问题,用二维数组存储会有较多的无用数据,因此我们引入了稀疏数组
- 处理方法:
- 记录数组共有几行几列、有多少个不同的值
- 把具有不同值的元素的行、列以及值记录在一个规模较小的二维数组中
- 实现思路
- 二维数组转稀疏数组
- 获取二维数组的行数和列数以及有效数据的个数sum
- 根据有效数据的个数sum创建稀疏数组
- 将二维数组的有效数据的行、列以及值放入稀疏数组中
- 稀疏数组转化为二维数组
- 读取稀疏数组的第一行,根据行、列创建二维数组
- 将有效数据放入二维数组中对应的位置
- 二维数组转稀疏数组
- 代码实现
//创建二维数组
var arr = new Array(10);
for(let i=0;i<arr.length;i++){
arr[i]=new Array(10);
}
//给二维数组赋初值
arr[1][2]=1;
arr[2][3]=2;
arr[0][0]=3;
//遍历打印二维数组
for(let i=0;i<arr.length;i++){
for(let j=0;j<arr[i].length;j++){
if(arr[i][j]==undefined){
document.write(0);
}else{
document.write(arr[i][j]);
}
}
document.write("<br />");
}
document.write("<br />");
document.write("<br />");
//开始转为稀疏数组
//先获取有效数据个数
var sum = 0;
for(let i=0;i<arr.length;i++){
for(let j=0;j<arr[i].length;j++){
if(arr[i][j]!=undefined){
sum++;
}
}
}
//根据有效数据个数创建数组
var sparseArr = new Array(sum+1);
for(let i=0;i<sparseArr.length;i++){
sparseArr[i]=new Array(3);
}
//给稀疏数组第一行赋值
sparseArr[0][0] = arr.length;
sparseArr[0][1] = arr[0].length;
sparseArr[0][2] = sum;
//遍历二维数组,并把所有有效数据的行、列、值放入稀疏数组中
var a = 1;
for(let i=0;i<arr.length;i++){
for(let j=0;j<arr[i].length;j++){
if(arr[i][j]!=undefined){
sparseArr[a][0] = i;
sparseArr[a][1] = j;
sparseArr[a++][2] = arr[i][j];
}
}
}
//遍历稀疏数组
for(let i=0;i<sparseArr.length;i++){
for(let j=0;j<sparseArr[i].length;j++){
document.write(sparseArr[i][j]+" ");
}
document.write("<br />")
}
document.write("<br />")
document.write("<br />")
//开始转化为二维数组
//根据稀疏数组第一行创建二维数组
var arr1 = new Array(sparseArr[0][0]);
for(let i=0;i<arr1.length;i++){
arr1[i]=new Array(sparseArr[0][1]);
}
//根据稀疏数组给二维数组赋值
for(let i=1;i<sparseArr.length;i++){
arr1[sparseArr[i][0]][sparseArr[i][1]]=sparseArr[i][2];
}
//遍历新的二维数组
for(let i=0;i<arr1.length;i++){
for(let j=0;j<arr1[i].length;j++){
if(arr1[i][j]==undefined){
document.write(0);
}else{
document.write(arr1[i][j]);
}
}
document.write("<br />");
}
队列(queue)
- 引入:银行排队叫号问题
- 处理方法:
- 有序列表,可以是数组或者链表
- 遵循先入先出的原则
- 实现方式
- 由一个对象组成队列,对象中包括四个属性:一个数组、队首指针(保存下标)、队尾指针、数据数量
- 三个方法:创建队列、增加元素、取出元素
- 代码实现
var wait = [];//等待数组
//先创建一个队形
var queue = {
maxLength:10,//队列长度
dataCount:0,//队列中数据个数
arr:[],
front:0,//队首指针
rear:0,//队尾指针
createQueue:function(){
this.arr=new Array(this.maxLength);
},
addObj:function(obj){//移动队尾指针
if((this.dataCount!=0&&this.rear-this.front==0)){//队列已满
document.write("队列已满");
wait.push(obj);//队列已满在等待数组中等待
document.write("<br />");
}else{
this.arr[this.rear]=obj;//将数据放入队列
this.dataCount++;//队列内数据数量+1
this.rear=(this.rear+1)%this.maxLength;//队尾指针向后移,若移到数组最后则从头开始
// 遍历队列,显示所有元素
for(let i=0;i<this.arr.length;i++){
if(this.arr[i]==undefined){
document.write(0+" ");
}else{
document.write(this.arr[i]+" ");
}
}
document.write("<br />");
}
},
removeObj:function(){
if(this.dataCount==0){//队列为空
document.write("队列为空");
document.write("<br />");
}else{
let data;
this.dataCount--;//队列内数据-1
data=this.arr[this.front];//取出该数据
document.write(this.arr[this.front]);
document.write("<br />");
this.arr[this.front]=undefined;//将该数据所在位置初始化
this.front=(this.front+1)%this.maxLength;//队首指针向后移
let temp =wait.shift();//取出并删除等待数组中的第一个元素,这里不可以用pop因为js中的pop是取出最后一个元素,相当于出栈
if(temp!=undefined){
this.addObj(temp);//将从等待数组中取得的数据放入队列中
}
//遍历队列
for(let i=0;i<this.arr.length;i++){
if(this.arr[i]==undefined){
document.write(0+" ");
}else{
document.write(this.arr[i]+" ");
}
}
document.write("<br />");
//返回数据
return data;
}
}
}
//测试数据
queue.createQueue();
queue.addObj(1);
queue.addObj(2);
queue.addObj(3);
queue.addObj(4);
queue.addObj(4);
queue.addObj(4);
queue.addObj(4);
queue.addObj(4);
queue.addObj(4);
queue.addObj(4);
queue.addObj(4);//队列已满进入等待队列
var data = queue.removeObj();//有元素出队列,等待数组进入队列
console.log(data);
queue.removeObj();
queue.removeObj();
queue.removeObj();
queue.removeObj();
queue.addObj(9);
queue.addObj(9);
queue.addObj(9);
queue.addObj(9);
queue.addObj(9);
queue.addObj(9);
queue.removeObj();
queue.removeObj();
queue.removeObj();
queue.removeObj();
queue.removeObj();
queue.removeObj();
链表(linked list)
- 处理方式
- 链表以结点存储,每个节点都包括数据域data和next域指向下一个节点
- 链表又有带头结点的链表和不带节点的链表,头节点不保存数据只用于标识链表的开始
- 实现方式
- 用一个类表示链表,类中要包括头节点,头节点的next用于指向后来加入链表的节点
- 遍历:创建一个辅助变量用于遍历整个链表
- 代码实现
//存储数据的类
class Data{
constructor(id,name,desc) {
this.id=id;
this.name=name;
this.desc=desc;
this.next=null;
}
}
//单链表结构
class LinkedList{
constructor(){
this.head = {
id:'',
name:'',
desc:'',
next:null
}
}
//添加一个节点
add(obj){
let temp=this.head;
while(true){
if(temp.next==null){
temp.next=obj;
break;
}
temp=temp.next;
}
}
//显示整个链表
show(){
let temp=this.head.next;
while(true){
document.write(temp.id+","+temp.name+","+temp.desc+"<br />");
if(temp.next==null){
break;
}
temp=temp.next;
}
}
//根据id大小插入节点
insert(obj){
let temp =this.head;
let id= obj.id;
while(true){
if(temp.next!=null){
if(temp.next.id>=id){
obj.next=temp.next;
temp.next=obj;
break;
}
temp=temp.next;
}else{
temp.next=obj;
break;
}
}
}
//根据节点id插入节点
changeNode(id,name){
let temp = this.head;
while(true){
if(temp.id==id){
temp.name=name;
break;
}else if(temp.next==null){
break;
}else{
temp=temp.next;
}
}
}
//删除节点
deleteNode(id){
let temp = this.head;
while(true){
if(temp.next.id==id){
temp.next=temp.next.next;
break;
}else if(temp.next==null){
break;
}else{
temp=temp.next;
}
}
}
//反转链表
reverse(){
let head= this.head;
if(head.next==null){return;}
if(head.next.next==null){return;}
var a=head.next.next.next;
var b=head.next.next;
var c=head.next;
c.next=null;
while(a){
b.next=c;
c=b;
b=a;
a=a.next;
}
b.next=c;
this.head.next=b;
}
}
//测试数据
var data1 = new Data(1,"张三","高兴哈哈哈");
var data2 = new Data(2,"李四","开心哈哈哈");
var data3 = new Data(3,"王五","大笑哈哈哈");
var data4 = new Data(4,"赵六","开怀哈哈哈");
var list = new LinkedList();
// list.add(data4);
// list.add(data1);
// list.add(data3);
// list.add(data2);
list.insert(data4);
list.insert(data1);
list.insert(data3);
list.insert(data2);
//list.deleteNode(1);
list.show();
list.reverse();
list.show();
双向链表(Two-way Linked List)
- 单链表只能单向遍历,不能实现自我删除,决定了我们要引入双向链表
- 双向链表的实现与单向链表类似,与之不同的地方在于双向链表的节点比单向链表多了一个指针用于指向前一个节点
- 代码实现
class Data{
constructor(id,name) {
this.pre=null;
this.id=id;
this.name=name;
this.next=null;
}
}
class TwoWayLinkedList{
constructor() {
this.head={
next:null
}
}
add(obj){
let temp = this.head;
while(true){
if(temp.next==null){
temp.next=obj;
obj.pre=temp;
break;
}
temp=temp.next;
}
}
show(){
let temp = this.head.next;
while(temp){
document.write(temp.id+","+temp.name+"<br />");
temp = temp.next;
}
document.write("<br />");
}
insert(obj){
let temp = this.head;
while(true){
if(temp.next==null){
temp.next=obj;
obj.pre=temp;
break;
}
if(temp.id>obj.id){
obj.pre=temp.pre
temp.pre.next=obj;
temp.pre=obj;
obj.next=temp;
break;
}
temp=temp.next;
}
}
delete(id){
let temp=this.head;
while(true){
if(temp.id==id){
temp.pre.next=temp.next;
if(temp.next!=null){
temp.next.pre=temp.pre;
}
break;
}
if(temp.next==null){
break;
}
temp=temp.next;
}
}
}
//测试
var data1 = new Data(1,"张三");
var data2 = new Data(2,"李四");
var data3 = new Data(3,"王五");
var data4 = new Data(4,"赵六");
var list = new TwoWayLinkedList();
// list.add(data1);
// list.add(data2);
// list.add(data3);
// list.add(data4);
list.insert(data1);
list.insert(data3);
list.insert(data4);
list.insert(data2);
list.delete(4)
list.show()
环形链表(Circular Linked List)
- 引入:约瑟夫问题:约瑟夫问题是个有名的问题:n个人围成一圈,从第k个开始报数,第m个将出列,最后剩下一个,其余人都将出列。求出列的顺序。该问题可由数组进行作答,亦可使用环形链表进行作答
- 处理方法
- 创建一个节点指向他自己
- 利用辅助节点添加节点,并始终让最后一个节点的next指向第一个节点,形成环路
- 代码实现(环形链表的操作和约瑟夫问题的解答)
class Node{
constructor(id) {
this.id=id;
this.next=null;
}
}
class CircularLinkedList{
constructor() {
this.head={
next:null
};
this.currentNode=null;
}
add(obj){
if(this.currentNode==null){
this.head.next=obj;
this.currentNode=obj;
obj.next=obj;
}
else{
this.currentNode.next=obj;
this.currentNode=obj;
obj.next=this.head.next;
// console.log(obj);
}
}
show(){
var temp=this.head.next;
console.log(this.head.next)
while(true){
if(temp==this.currentNode){
document.write(temp.id+"<br />");
break;
}else{
document.write(temp.id+"<br />");
temp=temp.next;
}
}
document.write("<br />");
document.write("<br />");
}
deleteNode(node){
var temp=this.head;
// var flag= 0;
while(true){
if(temp.next.id==node.id){
// flag= 1;
if(temp!=this.head){
temp.next=temp.next.next;
break;
}
else if(temp.next.next!=temp.next){//只剩唯一一个节点
this.head=null;
break;
}
else if(temp.next.next.next==temp.next){//只剩两个要删第一个
temp.next=temp.next.next;
temp.next.next=temp.next.next.next;
}
}
else{
temp=temp.next;
}
}
}
}
//约瑟夫问题的解答
function solve(n,k,m){
var list = new CircularLinkedList();
var sum= n;//表示现在队中还有多少小孩儿
var mm;
for(let i=0;i<n;i++){
var node = new Node(i+1);
list.add(node);
}
var temp=list.head.next;
var last=list.head;
k--;
while(k!=0){
k--;
temp=temp.next;
last=last.next;
}
while(sum--){
console.log(temp.id+"aaa");
mm=1;
while(mm!=m){
temp=temp.next;
mm++;
while(temp.id==0){
temp=temp.next;
}
}
document.write(temp.id+"<br />");
temp.id=0;
temp=temp.next;
if(sum!=0){
while(temp.id==0){
temp=temp.next;
}
}
}
}
solve(9,3,3);//总共九个人从第三个开始报数从1开始报到3就出列,一直报到最后一个人
栈(stack)
- 引入:计算机底层计算
- 处理方法:
- 先进后出的有序列表
- 只能在栈顶进行添加和删除操作,栈顶固定不变
- 入栈(push)、出栈(pop)
- 实现思路
- 使用数组或链表模拟栈
- 定义一个栈顶top
- 入栈 将数据添加到栈顶 top++
- 出栈 先获取到栈顶的数据然后将top–
- 代码实现
数组
class Stack{
constructor(length) {
this.stack=new Array(length);
this.length=length;
this.top=-1;
}
pushObj(obj){
if(this.top<this.length-1){
this.top++;
this.stack[this.top]=obj;
}else{
document.write("栈已满,无法压栈"+"<br />");
}
}
popObj(){
if(this.top!=-1){
var obj = this.stack[this.top--];
return obj;
}else{
document.write("栈空,无数据可出栈"+"<br />");
}
}
show(){
var temp=this.top;
if(temp==-1){
document.write("栈空"+"<br />");
}
while(temp!=-1){
document.write(this.stack[temp--]+"<br />");
}
}
}
var stack = new Stack(5);
stack.show();
// alert(stack.stack.length)
stack.pushObj(1);
stack.pushObj(2);
stack.pushObj(3);
stack.pushObj(4);
stack.pushObj(5);
stack.pushObj(6);
stack.show();
document.write("<br />")
var obj1=stack.popObj();
document.write(obj1+"<br />");
obj1=stack.popObj();
document.write(obj1+"<br />");
obj1=stack.popObj();
document.write(obj1+"<br />");
obj1=stack.popObj();
document.write(obj1+"<br />");
obj1=stack.popObj();
document.write(obj1+"<br />");
obj1=stack.popObj();
document.write(obj1+"<br />");
链表
class Data{
constructor(id) {
this.id=id;
this.next=null;
}
}
class LinkedListStack{
constructor() {
this.head={
next:null
}
this.top=null;
}
pushObj(obj){
if(this.top==null){
this.head.next=obj;
this.top=obj;
}else{
this.top.next=obj;
this.top=obj;
}
}
popObj(){
if(this.top==null){
return;
}
let temp = this.head;
while(temp.next.next!=null){
temp=temp.next;
}
var obj = temp.next;
this.top=temp;
temp.next=null;
if(temp==this.head){
this.top=null;
}
return obj;
}
show(){
document.write("<br />");
let temp = this.head.next;
while(temp){
document.write(temp.id+"<br />");
temp=temp.next;
}
}
}
var list = new LinkedListStack();
var obj1 = new Data(1);
var obj2 = new Data(2);
var obj3 = new Data(3);
var obj4 = new Data(4);
var obj5 = new Data(5);
list.pushObj(obj1);
list.pushObj(obj2);
list.pushObj(obj3);
list.pushObj(obj4);
list.pushObj(obj5);
list.show();
var obj = list.popObj();
document.write(obj.id+"<br />");
obj = list.popObj();
document.write(obj.id+"<br />");
obj = list.popObj();
document.write(obj.id+"<br />");
obj = list.popObj();
document.write(obj.id+"<br />");
obj = list.popObj();
document.write(obj.id+"<br />");
计算器计算过程https://blog.csdn.net/qq_45147812/article/details/106860174
前缀表达式、后缀表达式、中缀表达式
- 前缀表达式的运算符位于操作数之前,例如
(3+4)*5-6
的前缀表达式为-*+3456
- 计算过程
- 从左向右扫描,遇到数字将数字进栈,遇到运算符则弹出栈顶的两个数,执行运算,并将结果入栈,重复上述过程,直至表达式扫描到最左端,最后运算出的值即为表达式的结果
- 从左向右扫描,遇到数字将数字进栈,遇到运算符则弹出栈顶的两个数,执行运算,并将结果入栈,重复上述过程,直至表达式扫描到最左端,最后运算出的值即为表达式的结果
- 计算过程
- 中缀表达式即为我们所熟知的算数表达式,但是这种算数表达式对于计算机而言比较麻烦,我们我们要先将中缀表达式改成前缀表达式或者或追表达式
- 后缀表达式与前缀表达式类似,但运算符位于操作数之后,例如
(3+4)*5-6
的后缀表达式为34+5*6-
- 计算过程
- 从左到右扫描表达式,遇到数字就将数字入栈,遇到运算符时弹出栈顶的两个数进行运算并入栈,重复上述过程,直至扫描到表达式的最右端,最后运算的值即为表达式的结果
- 从左到右扫描表达式,遇到数字就将数字入栈,遇到运算符时弹出栈顶的两个数进行运算并入栈,重复上述过程,直至扫描到表达式的最右端,最后运算的值即为表达式的结果
- 计算过程
中缀表达式转化为后缀表达式
- 实现方式
- 初始化两个栈,运算符栈s1和储存中间结果的栈s2
- 从左到右扫描中缀表达式
- 遇到数字将其压栈s2
- 遇到运算符
- 若s1为空或者为左括号,则直接进栈
- 若优先级高于栈顶符号,压入栈s1(若此时栈顶为左括号也直接进栈)
- 否则见s1的运算符压入栈s2
- 遇到括号
- 左括号直接进栈
- 右括号一直从s1出栈,压到s2直达找到左括号
- 重复步骤2-5,直到中缀表达式被扫描完成
- 将s1栈中剩余的符号压入s2
- 依次弹出s2的元素
- 代码实现
//判断谁的优先级高
function judge(ch1,ch2){//ch1为当前运算符
if(ch2=="("){return 0;}
// if(ch1==ch2){//ch1与ch2相等
// return 1;
// }else if((ch1=='+'||ch1=='-')&&(ch2=='*'||ch2=='/')){//ch1小于
// return 1;
// }else if((ch1=='*'||ch1=='/')&&(ch2=='*'||ch2=='/')){//ch1与ch2相等
// return 1;
// }else if((ch1=='+'||ch1=='-')&&(ch2=='+'||ch2=='-')){//ch1与ch2相等
// return 1;
// }else{
// return 0;
// }
if((ch1=='*'||ch1=='/')&&(ch2=='+'||ch2=='-')){return 0;}else{return 1;}
}
function alter(str){
var s1 = new LinkedListStack();
var s2 = new LinkedListStack();
var ss = new Array();
for(let i=0;i<str.length;i++){
let ch = str[i];
if(!isNaN(ch)){
//如果是数字直接入栈s2
// let obj = new Data(ch);
// s2.pushObj(obj);
var n=str[i];
while(!isNaN(str[i+1])){
i++;
n = n*10 + parseInt(str[i]);
}
s2.pushObj(new Data(n.toString()));
}else{
var o = s1.popObj();
if(o==undefined){//如果栈空直接入栈
console.log("kong")
let obj = new Data(ch);
s1.pushObj(obj);
ss.push(obj.ch);
continue;
}else{
s1.pushObj(o);
// s1.show()
if(ch=='('){
//如果是左括号直接入栈s1
let obj = new Data(ch);
s1.pushObj(obj);
ss.push(obj.ch)
}else if(ch==')'){
ss.push(ch)
var obj = s1.popObj();
while(obj.ch!='('){
s2.pushObj(obj);
obj = s1.popObj();
if(obj==undefined){break;}
}
// while(s1.top.ch!='('){
// s2.pushObj(s1.popObj());
// }
// s1.popObj()
}else{
var cc=s1.popObj().ch;
if(judge(ch,cc)==0){//优先级高于栈顶符号的优先级,压入栈s1
s1.pushObj(new Data(cc));
let obj = new Data(ch);
s1.pushObj(obj);
ss.push(obj.ch)
}else{
console.log(cc)
let obj = new Data(cc);
s2.pushObj(obj);
i--;
continue;
}
}
}
}
}
while(s1.top){
let obj = s1.popObj();
s2.pushObj(obj);
}
console.log(ss)
// document.write("<br />")
// document.write("<br />")
// document.write("<br />")
s2.show();
var str = [];
while(s2.top){
let obj = s2.popObj();
str.push(obj.ch);
}
return str.reverse();
}
//测试
var result = alter("(3+4)*5-6");
console.log(result)
逆波兰计算器的实现https://blog.csdn.net/qq_45147812/article/details/106877837
哈希表(散列)
-
根据关键码值进行访问
-
可以用数组+链表或者数组+二叉树实现
-
图示
-
实现思路
- 创建一个保存数据的节点类Data,创建一个链表类保存链表,创建一个数组数组的每个元素为一个链表类的实例
- 每次添加要根据相应的散列函数计算出对应的数组下标
-
代码实现
class Data{
constructor(id,name,dept) {
this.id=id;
this.name=name;
this.dept=dept;
this.next=null;
}
}
class DataLinkedList{
constructor(id) {
this.linlkedid=id;
this.head={
next:null
}
}
addData(obj){
let temp = this.head;
while(temp.next!=null){
temp = temp.next;
}
temp.next=obj;
}
show(){
var temp = this.head.next;
if(temp==null){
document.write("空<br\ />")
}
while(temp){
document.write(temp.id+" "+temp.name+" "+temp.dept+"<br\ />")
temp=temp.next;
}
document.write("<br\ />")
}
find(id){
let temp = this.head.next;
while(temp&&temp.id!=id){
temp=temp.next;
}
if(temp&&temp.id==id){
document.write("<br\ />"+"找到了"+temp.id+" "+temp.name+" "+temp.dept+"<br\ />")
}else{
document.write("<br\ />"+"没有找到id为"+id+"的数据 "+"<br\ />")
}
}
}
class HashTable{
constructor(size){
this.size=size;
this.arr=new Array(size);
for(let i=0;i<size;i++){
this.arr[i]=new DataLinkedList(i);
}
}
addObj(obj){
let linlkedid = obj.id%this.size;
console.log(linlkedid)
this.arr[linlkedid].addData(obj);
}
showAll(){
for(let i=0;i<this.size;i++){
this.arr[i].show();
}
}
findObj(id){
let linkedid= id%this.size;
console.log(linkedid)
this.arr[linkedid].find(id)
}
}
var hashTable = new HashTable(5);
hashTable.addObj(new Data(1,"xixi","信息工程学院"))
hashTable.addObj(new Data(12,"haha","信息工程学院"))
// hashTable.arr[1].find(1)
hashTable.showAll()
hashTable.findObj(123)
树
- 树结构可以提高数据存储、读取的效率,既可以保证数据检测的速度也可以保证数据插入的速度
- 相关概念:节点、根节点、父节点、子节点、层、路径、子树、叶子节点(没有子节点的节点)、子节点的权、树的高度(最大层数)
二叉树
- 每个节点最多只有两个子节点的树
- 满二叉树 所有叶子节点都在最后一层,且节点总数为2(n为层数)
- 完全二叉树 所有二叉树的叶子节点都在最后一层或倒数第二层,且最后一层左边连续,倒数第二层右边连续
- 遍历 前序、中序、后序(根据输出父节点的时机)
- 前序 父节点 左子树 右子树
- 中序 左子树 父节点 右子树
- 后序 左子树 右子树 父节点
- 实现:利用节点类和二叉树类共同实现
- 代码实现(包括了遍历、查找和删除,删除以右节点代替)
class Data{
constructor(id) {
this.id=id;
this.left=null;
this.right=null;
}
preOrder(){
document.write(this.id+' ');
if(this.left){
this.left.preOrder();
}
if(this.right){
this.right.preOrder();
}
}
infixOrder(){
if(this.left){
this.left.infixOrder();
}
document.write(this.id+' ');
if(this.right){
this.right.infixOrder();
}
}
postOrder(){
if(this.left){
this.left.postOrder();
}
if(this.right){
this.right.postOrder();
}
document.write(this.id+' ');
}
deleteData(id){
if(this.left){
if(this.left.id==id){
if(this.left.right==null){
this.left=this.left.left;
}else{
if(this.left.left){
let temp = this.left.right;
while(temp.left){
temp = temp.left;
}
temp.left=this.left.left;
}
this.left=this.left.right
}
}else{
this.left.deleteData(id);
}
}
if(this.right){
if(this.right.id==id){
if(this.right.right==null){
this.right=this.right.left;
}else{
if(this.right.left){
let temp = this.right.right;
while(temp.left){
temp = temp.left;
}
temp.left=this.right.left;
}
this.right=this.right.right
}
}else{
this.right.deleteData(id);
}
}
}
find(id){
let temp = this;
if(temp.id==id){document.write("找到了"+temp.id+"<br />");}
while(temp.left){
temp=temp.left;
if(temp.id==id){
document.write("找到了"+temp.id+"<br />");
return;
}
}
while(temp.right){
temp=temp.right;
if(temp.id==id){
document.write("找到了"+temp.id+"<br />");
return;
}
}
document.write("没有找到")
}
}
class BinaryTree{
constructor() {
this.root = new Data;
}
preOrder(){
if(this.root==undefined){
document.write("二叉树为空");
return ;
}
this.root.preOrder();
}
infixOrder(){
if(this.root==undefined){
document.write("二叉树为空");
return ;
}
this.root.infixOrder();
}
postOrder(){
if(this.root==undefined){
document.write("二叉树为空");
return ;
}
this.root.postOrder();
}
add(obj){
let temp = this.root;
if(temp.id==undefined){
this.root=obj;
}else{
}
}
deleteNode(id){
this.root.deleteData(id);
}
preFind(id){
this.root.find(id);
}
}
var tree = new BinaryTree();
var root = new Data(-1);
var node1 = new Data(0);
var node2 = new Data(5);
var node3 = new Data(1);
var node4 = new Data(3);
var node5 = new Data(2);
var node6 = new Data(4);
root.right=node1;
node1.right=node2;
node2.left=node3;
node2.right=node4;
node4.left=node5;
node4.right=node6;
tree.add(root);
console.log(tree)
tree.postOrder()
document.write("<br />")
document.write("<br />")
// tree.deleteNode(3);
// tree.postOrder()
tree.preFind(15)
顺序存储二叉树
- 树与数组是可以相互转换的,转换为数组且数组可以展现树的前序中序后序遍历即为顺序存储二叉树
- 顺序存储二叉树的特点
- 顺序存储二叉树通常只考虑完全二叉树
- 第n个元素左子节点为2*n+1.
- 第n个元素右子节点为2*n+2
- 第n个元素的父节点为(n-1)/2
- n表示二叉树中第几个元素(从0开始)
- 图示
- 代码实现
var treeArr = [0,1,2,3,4,5,6];
function preOrder(arr,index){
if(arr==null||arr.length<0){
document.write("空");
return;
}
document.write(index+" ");
if((index*2+1)<arr.length){
preOrder(arr,index*2+1);
}
if((index*2+2)<arr.length){
preOrder(arr,index*2+2);
}
}
preOrder(treeArr,0);
线索二叉树
- n个节点含有n+1个空指针域(2n-(n-1)),利用二叉链表中的空指针存放该节点在某种遍历次序下的前驱结点和后继节点(实际开发不怎么用的到,只看到了路由器会使用到)
- 图示
堆
- 堆是一个完全二叉树,它具有一下条件:
- 大顶堆 每个节点的值都大于其左右孩子节点的值
- 小顶堆 每个节点的值都小于其左右孩子节点的值
- 基本思想
- 将排序数组构造成一个大顶堆
- 整个序列的最大值就是堆顶的根节点
- 图示
赫夫曼树
- 给定n个叶子节点,构造一个二叉树,若该树的带权路径长度(wpl)达到最小,则这样的二叉树成为最优二叉树,权值较大的节点离根节点最近
- 带权路径长度=权*路径长度
- 树的带权路径长度规定为所有叶子节点的带权路径长度之和
- 权值最大的节点离根节点越近的二叉树是最优二叉树(WPL最小的是赫夫曼树)
- 图示(中间的树即为赫夫曼树)
- 实现思路
- 从小到大进行排序,每一个数据都是一个节点,每个节点看成一棵树
- 取出根节点权值最小的两棵树
- 组成一个新的二叉树,且根节点的值时原来两个二叉树根节点值的和
- 再将这棵二叉树以根节点的权值大小进行排序,重复以上操作
- 图示
- 代码实现
var arr = [13,7,8,3,29,6,1];
class Node{
constructor(id) {
this.id= id;
this.left=null;
this.right=null;
}
}
function sortBy(a,b){//nodes数组排序的规则从小到大排序
return a.id-b.id;
}
var result = createHuffManTree(arr);
console.log(result)
function createHuffManTree(arr){
var nodes = new Array();
for(let i=0;i<arr.length;i++){
nodes.push(new Node(arr[i]));
}
nodes.sort(sortBy)
while(nodes.length>1){
var root = new Node(nodes[0].id+nodes[1].id);
root.left=nodes.shift();
root.right=nodes.shift();
nodes.push(root)
nodes.sort(sortBy)
}
console.log(nodes)
return nodes[0];
}
二叉排序树
- 对于二叉排序树的任何非叶子节点要求左子节点的值比当前节点的值小右子节点的值比当前节点的值大(值相同时可左可右)
- 思路分析 创建一个二叉树,依次照大小关系插入二叉树
- 代码实现(生成、添加、遍历和删除)
var arr = [7,3,10,1,5,8,11];
class Node{
constructor(id) {
this.id=id;
this.left=null;
this.right=null;
}
position(obj){
if(this.id>obj.id){
if(this.left==null){
this.left=obj;
}else{
this.left.position(obj);
}
}else{
if(this.right==null){
this.right=obj;
}else{
this.right.position(obj);
}
}
}
preOrder(){
document.write(this.id+"<br>");
if(this.left){
this.left.preOrder();
}
if(this.right){
this.right.preOrder();
}
}
deleteData(id){
if(this.left){
if(this.left.id==id){
if(this.left.right==null){
this.left=this.left.left;
}else{
if(this.left.left){
let temp = this.left.right;
while(temp.left){
temp = temp.left;
}
temp.left=this.left.left;
}
this.left=this.left.right
}
}else{
this.left.deleteData(id);
}
}
if(this.right){
if(this.right.id==id){
if(this.right.right==null){
this.right=this.right.left;
}else{
if(this.right.left){
let temp = this.right.right;
while(temp.left){
temp = temp.left;
}
temp.left=this.right.left;
}
this.right=this.right.right
}
}else{
this.right.deleteData(id);
}
}
}
}
class binaryTree{
constructor() {
this.root=null;
}
init(arr){
for(let i=0;i<arr.length;i++){
let node = new Node(arr[i]);
this.addObj(node);
}
}
addObj(obj){
if(this.root==null){
this.root=obj;
}else{
let temp = this.root;
temp.position(obj);
}
}
preOrder(){
if(this.root==null){
return;
}
this.root.preOrder()
}
deleteObj(id){
this.root.deleteData(id)
}
}
var treeSet = new binaryTree();
treeSet.init(arr);
console.log(treeSet);
treeSet.preOrder();
document.write("----------------<br />")
treeSet.deleteObj(5)
treeSet.preOrder();
平衡二叉树
- 平衡二叉树也是二叉排序树
- 特点 是一棵空树或者左右两个子树的高度差绝对值不超过1,并且左右两个子树都是平衡二叉树
- 思路分析
- 左旋转
- 先创建一个新的节点newNode并将新节点的值设为当前节点的值
newNode.id=this.id
- 新节点的左指针指向当前节点的左子树
newNode.left=this.left
- 新节点的右指针指向当前节点的右子节点的左子树
newNode.right=this.right.left
- 当前节点的值改为当前节点右子节点的值
this.id=this.right.id
- 当前节点的右指针指向当前节点的右子节点的右子树
this.right = this.right.right
- 当前节点的左指针指向新节点
this,left=newNode
- 先创建一个新的节点newNode并将新节点的值设为当前节点的值
- 右旋转同上
- 什么时候进行旋转
- 左子树高度-右子树高度>1进行右旋转
- 若满足右旋转条件时,若它的左子树的右子树大于他的右子树的高度要先对当前这个节点的左节点进行左旋转,再对当前节点进行右旋转
-左旋转同上
- 若满足右旋转条件时,若它的左子树的右子树大于他的右子树的高度要先对当前这个节点的左节点进行左旋转,再对当前节点进行右旋转
- 左子树高度-右子树高度>1进行右旋转
- 统计树的高度、左右子树的高度
- 左旋转
height(){
return Math.max(this.left==null?0:this.left.height(),this.right==null?0:this.right.height())+1;
}
leftHeight(){
if(this.left==null){return 0;}
return left.height();
}
rightHeight(){
if(this.right==null){return 0;}
return right.height();
}
- 图示
- 代码实现
var arr = [4,3,6,5,7,8];
class Node{
constructor(id) {
this.id=id;
this.left=null;
this.right=null;
}
position(obj){
if(this.id>obj.id){
if(this.left==null){
this.left=obj;
}else{
this.left.position(obj);
}
}else{
if(this.right==null){
this.right=obj;
}else{
this.right.position(obj);
}
}
if(this.leftHeight()-this.rightHeight()>1){
if(this.left.rightHeight()>this.rightHeight()){
if(this.left!=null){
this.left.leftRotate();
}
}
this.rightRotate();
return;
}
if(this.rightHeight()-this.leftHeight()>1){
if(this.right.leftHeight()>this.leftHeight()){
if(this.right!=null){
this.right.rightRotate();
}
}
this.leftRotate();
return;
}
}
preOrder(){
document.write(this.id+"<br>");
if(this.left){
this.left.preOrder();
}
if(this.right){
this.right.preOrder();
}
}
deleteData(id){
if(this.left){
if(this.left.id==id){
if(this.left.right==null){
this.left=this.left.left;
}else{
if(this.left.left){
let temp = this.left.right;
while(temp.left){
temp = temp.left;
}
temp.left=this.left.left;
}
this.left=this.left.right
}
}else{
this.left.deleteData(id);
}
}
if(this.right){
if(this.right.id==id){
if(this.right.right==null){
this.right=this.right.left;
}else{
if(this.right.left){
let temp = this.right.right;
while(temp.left){
temp = temp.left;
}
temp.left=this.right.left;
}
this.right=this.right.right
}
}else{
this.right.deleteData(id);
}
}
}
height(){
return Math.max(this.left==null?0:this.left.height(),this.right==null?0:this.right.height())+1;
}
leftHeight(){
if(this.left==null){return 0;}
return this.left.height();
}
rightHeight(){
if(this.right==null){return 0;}
return this.right.height();
}
leftRotate(){
let newNode = new Node(this.id);
newNode.left=this.left;
newNode.right=this.right.left;
this.id=this.right.id;
this.right=this.right.right;
this.left=newNode;
}
rightRotate(){
let newNode = new Node(this.id);
newNode.right=this.right;
newNode.left=this.left.right;
this.id=this.left.id;
this.left=this.left.left;
this.right=newNode;
}
}
class binaryTree{
constructor() {
this.root=null;
}
init(arr){
for(let i=0;i<arr.length;i++){
let node = new Node(arr[i]);
this.addObj(node);
}
}
addObj(obj){
if(this.root==null){
this.root=obj;
}else{
let temp = this.root;
temp.position(obj);
}
}
preOrder(){
if(this.root==null){
return;
}
this.root.preOrder()
}
deleteObj(id){
this.root.deleteData(id)
}
}
var treeSet = new binaryTree();
treeSet.init(arr);
console.log(treeSet);
treeSet.preOrder();
多叉树
- 允许每个节点又更多的数据项和更多的子节点
B树、B+树、B*树
- B树通过重新组织节点降低树的高度,并且减少io读写次数来提升效率
- B树广泛应用于文件存储系统以及数据库中
- B树的阶 节点最多子节点的个数
- B树的搜索,从根节点开始对接点内关键字序列进行二分查找,若命中则结束,否则进入查询关键字所在范围的儿子节点,重复直至儿子节点为空或已经是叶子节点
- 关键字集合分布在整棵树中,即叶子节点和非叶子节点都存放数据
- B+树是B树变体也是一种多路搜索树
- B+树的所有关键字都出现在叶子节点的链表中
- B*树是B+树的变体,在B+树非根节点和非叶子节点再增加指向兄弟的指针
- 图示
2-3树
- 2-3树是简单的的B树
- 所有叶子节点都在同一层(B树都满足)
- 2-3树是由二节点和三节点构成的树(有三个子节点的节点成为三节点)
- 2-3树的插入规则
- 所有叶子节点都在同一层
- 二节点要么没有节点要么两个节点
- 三节点要么没有节点要么三个节点
- 如插入时不能满足前三条,则需要拆,先向上拆,若上层满则拆本层,拆后必须满足前三条
- 三节点子树的值大小必须满足BST(二叉排序树)的规则
- 图示
图
- 图用于处理多对多的关系
- 概念 顶点、边、路径、无向图、有向图、带权图
- 表示方式 邻接矩阵、邻接表
- 邻接矩阵 表示图形中顶点之间相邻关系的矩阵(很多边不存在造成空间损失)
- 邻接表 只关心存在的边,不关心不存在的边
- 图示
- 深度优先遍历
- 从初始访问节点开始,可能有多个邻接节点,深度优先 遍历的策略为首先访问第一个邻接节点,然后再以这个被访问的邻接节点作为初始节点,访问他的第一个邻接节点
- 实现思路
- 访问初始节点v并标记节点v为已访问
- 查找节点v的第一个邻接节点w
- 若w存在则继续执行下一步,若不存在则需回溯重新执行
- 若w未被访问对w进行深度优先遍历递归
- 若已被访问则查找w邻接节点的下一个邻接节点,从第三步开始
- 代码实现
class Gragh{
constructor(n,arr) {
this.vertexList = arr ;
this.edges = new Array(n);
this.visited= new Array(n);
for(let i=0;i<n;i++){
this.edges[i]= new Array(n);
this.visited[i]=false;
}
this.edgesNum = 0;
}
insertVertex(ch){
this.vertexList.push(ch);
this.edges.push(new Array(this.vertexList.length))
}
insertEdge(v1,v2,weight){
this.edges[v1][v2]=weight;
this.edges[v2][v1]=weight;
this.edgesNum++;
}
showGragh(){
for(let i=0;i<this.vertexList.length;i++){
for(let j=0;j<this.vertexList.length;j++){
if(this.edges[i][j]!=undefined){
document.write(this.edges[i][j]+" ");
}else{
document.write("0 ");
}
}
document.write("<br />");
}
}
getFirstNeighbor(v1){//获得v1的第一个邻接节点
for(let i=0;i<this.vertexList.length;i++){
if(this.edges[v1][i]!=undefined){
return i;
}
}
return -1;
}
getNextNeighbor(v1,v2){//获得v1的v2节点后的邻接节点
for(let i=v2+1;i<this.vertexList.length;i++){
if(this.edges[v1][i]!=undefined){
return i;
}
}
return -1;
}
dfs(index){
document.write(this.vertexList[index]+" -> ");
this.visited[index]=true;
var w = this.getFirstNeighbor(index);
while(w!=-1){
if(this.visited[w]==false){
this.dfs(w);
}
w= this.getNextNeighbor(index,w);
}
}
dfsAll(){
//遍历所有节点进行回溯
for(let i=0;i<this.vertexList.length;i++){
if(this.visited[i]==false){
this.dfs(i);
}
}
}
}
var gragh = new Gragh(6,['A','B','C','D','E','F']);
gragh.insertEdge(0,1,1);
gragh.insertEdge(0,2,1);
gragh.insertEdge(0,3,1);
gragh.insertEdge(0,4,1);
gragh.insertEdge(1,4,1);
gragh.insertEdge(2,4,1);
gragh.insertEdge(2,5,1);
gragh.insertEdge(3,5,1);
gragh.insertEdge(4,5,1);
// gragh.showGragh();
gragh.dfsAll();
- 广度优先
- 类似于分层搜索,广度优先遍历需要使用一个队列以保持访问过得节点顺序,以便按照这个顺序访问这些邻接节点
- 实现思路
- 访问初始节点v标记为已访问
- 节点v入队列
- 若队列非空则继续执行,否则算法结束
- 出队列取得队头节点u
- 查找节点u的第一个邻接节点w
- 若w不存在则回溯重新执行,否则执行以下步骤
- 若我未被访问则访问节点w并标记为已访问
- 节点w入队列
- 查找节点u继w邻接节点的下一个邻接节点,并赋给w
- 图示
- 代码实现
class Gragh{
constructor(n,arr) {
this.vertexList = arr ;
this.edges = new Array(n);
this.visited= new Array(n);
for(let i=0;i<n;i++){
this.edges[i]= new Array(n);
this.visited[i]=false;
}
this.edgesNum = 0;
}
insertVertex(ch){
this.vertexList.push(ch);
this.edges.push(new Array(this.vertexList.length))
}
insertEdge(v1,v2,weight){
this.edges[v1][v2]=weight;
this.edges[v2][v1]=weight;
this.edgesNum++;
}
showGragh(){
for(let i=0;i<this.vertexList.length;i++){
for(let j=0;j<this.vertexList.length;j++){
if(this.edges[i][j]!=undefined){
document.write(this.edges[i][j]+" ");
}else{
document.write("0 ");
}
}
document.write("<br />");
}
}
getFirstNeighbor(v1){//获得v1的第一个邻接节点
for(let i=0;i<this.vertexList.length;i++){
if(this.edges[v1][i]!=undefined){
return i;
}
}
return -1;
}
getNextNeighbor(v1,v2){//获得v1的v2节点后的邻接节点
for(let i=v2+1;i<this.vertexList.length;i++){
if(this.edges[v1][i]!=undefined){
return i;
}
}
return -1;
}
bfs(i){
var queue = new Array();
document.write(this.vertexList[i]+"=>");
this.visited[i]=true;
queue.push(i);
while(queue.length>0){//队列未空
let u= queue.shift();
let w= this.getFirstNeighbor(u);
while(w!=-1){
if(this.visited[u]==false){
document.write(this.vertexList[w]+"=>");
this.visited[w]=true;
queue.push(w);
}
w=this.getNextNeighbor(u,w);
}
}
}
bfsAll(){
//遍历所有节点进行回溯
for(let i=0;i<this.vertexList.length;i++){
if(this.visited[i]==false){
this.bfs(i);
}
}
}
}
var gragh = new Gragh(6,['A','B','C','D','E','F']);
gragh.insertEdge(0,1,1);
gragh.insertEdge(0,2,1);
gragh.insertEdge(0,3,1);
gragh.insertEdge(0,4,1);
gragh.insertEdge(1,4,1);
gragh.insertEdge(2,4,1);
gragh.insertEdge(2,5,1);
gragh.insertEdge(3,5,1);
gragh.insertEdge(4,5,1);
// gragh.showGragh();
gragh.bfsAll();