导师计划已经开始一个月了,自己的讲解的课程选择了数据结构和算法。这个系列的讲解分为上下两章javascript语言辅助。本篇文章为上章,涉及的内容是基本的数据结构。在日本,晚上没事安排@…@,时间还是充足的...,于是自己整理下本系列知识点的上章内容。

以下为正文:

数据结构是计算机存储、组织数据的方式。数据结构是指相互直接存在一种或多种特殊关系的数据元素的集合。通常情况下,精心选择数据结构可以带来更高的运行或者存储效率。作为一名程序猿,更需要了解下数据结构。AND WHY?可以参考这篇文章【译】编程不容易中的性能和优化部分内容。

讲到数据结构,我们都会谈到线性结构和非线性结构。

1.线性结构是一个有序数据元素的集合。它应该满足下面的特征:

  • 集合中必存在唯一的一个“第一个元素”
  • 集合中必存在唯一的一个“最后的元素”
  • 除最后一元素之外,其它数据元素均有唯一的“后继”
  • 除第一个元素之外,其它数据元素均有唯一的“前驱”

按照百度百科的定义,我们知道符合条件的数据结构就有栈、队列和其它。

2.非线性结构其逻辑特征是一个节点元素可以有多个直接前驱或多个直接后继。

那么,符合条件的数据结构就有图、树和其它。

嗯~了解一下就行。我们进入正题:

数组

数组是一种线性结构,以十二生肖(鼠、牛、虎、兔、龙、蛇、马、羊、猴、鸡、狗、猪)排序为例:

我们来创建一个数组并打印出结果就一目了然了:

let arr = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪'];
arr.forEach((item, index) => {console.log(`[ ${index} ] => ${item}`);
});// [ 0 ] => 鼠
// [ 1 ] => 牛
// [ 2 ] => 虎
// [ 3 ] => 兔
// [ 4 ] => 龙
// [ 5 ] => 蛇
// [ 6 ] => 马
// [ 7 ] => 羊
// [ 8 ] => 猴
// [ 9 ] => 鸡
// [ 10 ] => 狗
// [ 11 ] => 猪
复制代码

数组中常用的属性和一些方法如下,直接调用相关的方法即可。这里不做演示~

常用的属性

  • length : 表示数组的长度

常用的方法

  • splice(index, howmany, item, ... itemx)

    splice方法自认为是数组中最强大的方法。可以实现数组元素的添加、删除和替换。参数index为整数且必需,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置;参数howmany为必需,为要删除的项目数量,如果设置为 0,则不会删除项目;item1, ... itemx为可选,向数组添加新的项目。

  • indexOf(searchValue, fromIndex)

    indexOf方法返回某个指定字符串值在数组中的位置。searchValue是查询的字符串;fromIndex是查询的开始位置,默认是0。如果查询不到,会返回-1。

  • concat(array1, ... arrayn)

    concat方法用于连接两个或者多个数组。

  • push(newElement1, ... newElementN)

    push方法可向数组的末尾添加一个或者多个元素。

  • unshift(newElement1, ... newElementN)

    unshift方法可向数组的开头添加一个或者多个元素。

  • pop()

    pop方法用于删除并返回数组的最后一个元素

  • shift()

    shift方法可以删除数组的第一个元素

  • reverse()

    reverse方法用于数组的反转

  • sort(sortFn)

    sort方法是对数组的元素排序。参数sortFn可选,其规定排序顺序,必须是函数。

let values = [0, 1, 5, 10, 15];
values.sort();
console.log(values); // [0, 1, 10, 15, 5]
// 为什么会出现这种排序结果呢❓
// 因为在忽略sortFn的情况下,元素会按照转换为字符串的各个字符的Unicode位点进行排序,如下
let equalValues = ['0', '1', '5', '10', '15'];
equalValues.sort();
console.log(equalValues); //  ["0", "1", "10", "15", "5"]let arr = [0, 10, 5, 1, 15];
function compare(el1, el2){return el1 - el2; // 升序排列
}
arr.sort(compare);
console.log(arr); // [0, 1, 5, 10, 15]arr.sort((el1, el2) => {return el2 - el1; // 降序排列
}); 
console.log(arr); // [15, 10, 5, 1, 0]
复制代码
  • forEach(fn(currentValue, index, arr), thisValue)

    forEach方法用于调用数组的每个元素,并将元素传递给回调函数。参数function(currentValue, index, arr){}是一个回调函数。thisValue可选,传递给函数的值一般用 "this" 值,如果这个参数为空, "undefined" 会传递给 "this" 值。

  • every(fn(currentValue, index, arr), thisValue)

    every方法用于检测数组中所有元素是否符合指定条件,如果数组中检测到有一个元素不满足,则整个表达式返回false,且剩余的元素不再检查。如果所有的元素都满足条件,则返回true

  • some(fn(currentValue,index,arr),thisValue)

    some方法用于检测数组中元素是否满足指定条件。只要有一个符合就返回true,剩余的元素不再检查。如果所有元素都不符合条件,则返回false

  • reduce(fn(accumulator, currentValue, currentIndex, arr), initialValue)

    reduce方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终为一个值。回调函数的四个参数的意义如下:accumulator,必需,累计器累计回调的返回值, 它是上一次调用回调时返回的累积值,或initialValue;currentValue,必需,数组中正在处理的元素;currentIndex,可选,数组中正在处理的当前元素的索引,如果提供了initialValue,则起始索引号为0,否则为1;arr,可选,当前元素所属的数组对象。initialValue,可选,传递给函数的初始值。

let arr = [1, 2, 3, 4];
let reducer = (accumulator, currentValue) => accumulator + currentValue;// 1 + 2 + 3 + 4
console.log(arr.reduce(reducer)); // 10// 5 + 1 + 2 + 3 + 4
console.log(arr.reduce(reducer, 5)); // 15
复制代码

是一种后进先出(LIFO)线性表,是一种基于数组的数据结构。(ps:其实后面讲到的数据结构或多或少有数组的影子)

  • LIFO(Last In First Out)表示后进先出,后进来的元素第一个弹出栈空间。类似于自动餐托盘,最后放上去的托盘,往往先被拿出来使用。
  • 仅允许在表的一端进行插入和移除元素。这一端被称为栈顶,相对地,把另一端称为栈底。如下图的标识。
  • 向一个栈插入新元素称作进栈、入栈或压栈,这是将新元素放在栈顶元素上面,使之成为新的栈顶元素。
  • 从一个栈删除元素又称为出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

我们代码写下,熟悉下栈:

class Stack {constructor(){this.items = [];}// 入栈操作push(element = ''){if(!element) return;this.items.push(element);return this;}// 出栈操作pop(){this.items.pop();return this;}// 对栈一瞥,理论上只能看到栈顶或者说即将处理的元素peek(){return this.items[this.size() - 1];}// 打印栈数据print(){return this.items.join(' ');}// 栈是否为空isEmpty(){return this.items.length == 0;}// 返回栈的元素个数size(){return this.items.length;}
}
let stack = new Stack(),arr = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪'];
arr.forEach(item => {stack.push(item);
});
console.log(stack.print()); // 鼠 牛 虎 兔 龙 蛇 马 羊 猴 鸡 狗 猪
console.log(stack.peek()); // 猪
stack.pop().pop().pop().pop();
console.log(stack.print()); // 鼠 牛 虎 兔 龙 蛇 马 羊
console.log(stack.isEmpty()); // false
console.log(stack.size()); // 8
复制代码

⚠️ 注意:栈这里的push和pop方法要和数组方法的push和pop方法区分下。

说到,这也让我想到了翻译的一篇文章JS的执行上下文和环境栈是什么?,感兴趣的话可以戳进去看下。

队列

队列是一种先进先出(FIFO)受限的线性表。受限体现在于其允许在表的前端(front)进行删除操作,在表的末尾(rear)进行插入【优先队列这些排除在外】操作。

代码走一遍:

class Queue {constructor(){this.items = [];}// 入队操作enqueue(element = ''){if(!element) return;this.items.push(element);return this;}// 出队操作dequeue(){this.items.shift();return this;}// 查看队前元素或者说即将处理的元素front(){return this.items[0];}// 查看队列是否为空isEmpty(){return this.items.length == 0;}// 查看队列的长度len(){return this.items.length;}// 打印队列数据print(){return this.items.join(' ');}
}let queue = new Queue(),arr = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪'];
arr.forEach(item => {queue.enqueue(item);
});
console.log(queue.print()); // 鼠 牛 虎 兔 龙 蛇 马 羊 猴 鸡 狗 猪
console.log(queue.isEmpty()); // false
console.log(queue.len()); // 12
queue.dequeue().dequeue();
console.log(queue.front()); // 虎
console.log(queue.print()); // 虎 兔 龙 蛇 马 羊 猴 鸡 狗 猪
复制代码

链表

在进入正题之前,我们先来聊聊数组的优缺点。

优点:

  • 存储多个元素,比较常用
  • 访问便捷,使用下标[index]即可访问

缺点:

  • 数组的创建通常需要申请一段连续的内存空间,并且大小是固定的(大多数的编程语言数组都是固定的),所以在进行扩容的时候难以掌控。(一般情况下,申请一个更大的数组,会是之前数组的倍数,比如两倍。然后,再将原数组中的元素复制过去)
  • 插入数据越是靠前,其成本很高,因为需要进行大量元素的位移。

相对数组,链表亦可以存储多个元素,而且存储的元素在内容中不必是连续的空间;在插入和删除数据时,时间复杂度可以达到O(1)。在查找元素的时候,还是需要从头开始遍历的,比数组在知道下表的情况下要快,但是数组如果不确定下标的话,那就另说了...

我们使用十二生肖来了解下链表:

链表是由一组节点组成的集合。每个节点都使用一个对象的引用指向它的后继。如上图。下面用代码实现下:

// 链表
class Node {constructor(element){this.element = element;this.next = null;}
}class LinkedList {constructor(){this.length = 0; // 链表长度this.head = new Node('head'); // 表头节点}/*** @method find 查找元素的功能,找不到的情况下直接返回链尾节点* @param { String } item 要查找的元素* @return { Object } 返回查找到的节点 */find(item = ''){let currNode = this.head;while(currNode.element != item && currNode.next){currNode = currNode.next;}return currNode;}/*** @method findPrevious 查找链表指定元素的前一个节点* @param { String } item 指定的元素* @return { Object } 返回查找到的之前元素的前一个节点,找不到节点的话返回链尾节点*/findPrevious(item){let currNode = this.head;while((currNode.next != null) && (currNode.next.element != item)){currNode = currNode.next;}return currNode;}/*** @method insert 插入功能* @param { String } newElement 要出入的元素* @param { String } item 想要追加在后的元素(此元素不一定存在)*/insert(newElement = '', item){if(!newElement) return;let newNode = new Node(newElement),currNode = this.find(item);newNode.next = currNode.next;currNode.next = newNode;this.length++;return this;}// 展示链表元素display(){let currNode = this.head,arr = [];while(currNode.next != null){arr.push(currNode.next.element);currNode = currNode.next;}return arr.join(' ');}// 链表的长度size(){return this.length;}// 查看链表是否为空isEmpty(){return this.length == 0;}/*** @method indexOf 查看链表中元素的索引* @param { String } element 要查找的元素*/indexOf(element){let currNode = this.head,index = 0;while(currNode.next != null){index++;if(currNode.next.element == element){return index;}currNode = currNode.next;}return -1;}/*** @method removeEl 移除指定的元素* @param { String } element */removeEl(element){let preNode = this.findPrevious(element);preNode.next = preNode.next != null ? preNode.next.next : null;}
}let linkedlist = new LinkedList();
console.log(linkedlist.isEmpty()); // true
linkedlist.insert('鼠').insert('虎').insert('牛', '鼠');
console.log(linkedlist.display()); // 鼠 牛 虎
console.log(linkedlist.find('猪')); // Node { element: '虎', next: null }
console.log(linkedlist.find('鼠')); // Node { element: '鼠', next: Node { element: '牛', next: Node { element: '虎', next: null } } }
console.log(linkedlist.size()); // 3
console.log(linkedlist.indexOf('鼠')); // 1
console.log(linkedlist.indexOf('猪')); // -1
console.log(linkedlist.findPrevious('虎')); // Node { element: '牛', next: Node { element: '虎', next: null } }
linkedlist.removeEl('鼠');
console.log(linkedlist.display()); // 牛 虎
复制代码

字典

字典的主要特点是键值一一对应的关系。可以比喻成我们现实学习中查不同语言翻译的字典。这里字典的键(key)理论上是可以使用任意的内容,但还是建议语意化一点,比如下面的十二生肖图:

class Dictionary {constructor(){this.items = {};}/*** @method set 设置字典的键值对* @param { String } key 键* @param {*} value 值*/set(key = '', value = ''){this.items[key] = value;return this;}/*** @method get 获取某个值* @param { String } key 键*/get(key = ''){return this.has(key) ? this.items[key] : undefined;}/*** @method has 判断是否含有某个键的值* @param { String } key 键*/has(key = ''){return this.items.hasOwnProperty(key);}/*** @method remove 移除元素* @param { String } key */remove(key){if(!this.has(key))  return false;delete this.items[key];return true;}// 展示字典的键keys(){return Object.keys(this.items).join(' ');}// 字典的大小size(){return Object.keys(this.items).length;}// 展示字典的值values(){return Object.values(this.items).join(' ');}// 清空字典clear(){this.items = {};return this;}
}let dictionary = new Dictionary(),// 这里需要修改arr = [{ key: 'mouse', value: '鼠'}, {key: 'ox', value: '牛'}, {key: 'tiger', value: '虎'}, {key: 'rabbit', value: '兔'}, {key: 'dragon', value: '龙'}, {key: 'snake', value: '蛇'}, {key: 'horse', value: '马'}, {key: 'sheep', value: '羊'}, {key: 'monkey', value: '猴'}, {key: 'chicken', value: '鸡'}, {key: 'dog', value: '狗'}, {key: 'pig', value: '猪'}];arr.forEach(item => {dictionary.set(item.key, item.value);});
console.log(dictionary.keys()); // mouse ox tiger rabbit dragon snake horse sheep monkey chicken dog pig
console.log(dictionary.values()); // 鼠 牛 虎 兔 龙 蛇 马 羊 猴 鸡 狗 猪
console.log(dictionary.has('dragon')); // true
console.log(dictionary.get('tiger')); // 虎
console.log(dictionary.remove('pig')); // true
console.log(dictionary.size()); // 11
console.log(dictionary.clear().size()); // 0
复制代码

集合

集合通常是由一组无序的,不能重复的元素构成。 一些常见的集合操作如图:

es6中已经封装好了可用的Set类。我们手动来写下相关的逻辑:

// 集合
class Set {constructor(){this.items = [];}/*** @method add 添加元素* @param { String } element * @return { Boolean }*/add(element = ''){if(this.items.indexOf(element) >= 0) return false;this.items.push(element);return true;}// 集合的大小size(){return this.items.length;}// 集合是否包含某指定元素has(element = ''){return this.items.indexOf(element) >= 0;}// 展示集合show(){return this.items.join(' ');}// 移除某个元素remove(element){let pos = this.items.indexOf(element);if(pos < 0) return false;this.items.splice(pos, 1);return true;}/*** @method union 并集* @param { Array } set 数组集合* @return { Object } 返回并集的对象*/union(set = []){let tempSet = new Set();for(let i = 0; i < this.items.length; i++){tempSet.add(this.items[i]);}for(let i = 0; i < set.items.length; i++){if(tempSet.has(set.items[i])) continue;tempSet.items.push(set.items[i]);}return tempSet;}/*** @method intersect 交集* @param { Array } set 数组集合* @return { Object } 返回交集的对象*/intersect(set = []){let tempSet = new Set();for(let i = 0; i < this.items.length; i++){if(set.has(this.items[i])){tempSet.add(this.items[i]);}}return tempSet;}/*** @method isSubsetOf 【A】是【B】的子集❓* @param { Array } set 数组集合* @return { Boolean } 返回真假值*/isSubsetOf(set = []){if(this.size() > set.size()) return false;this.items.forEach*(item => {if(!set.has(item)) return false;});return true;}
}let set = new Set(),arr = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴'];
arr.forEach(item => {set.add(item);
});
console.log(set.show()); // 鼠 牛 虎 兔 龙 蛇 马 羊 猴
console.log(set.has('猪')); // false
console.log(set.size()); // 9
set.remove('鼠');
console.log(set.show()); // 牛 虎 兔 龙 蛇 马 羊 猴
let setAnother = new Set(),anotherArr = ['马', '羊', '猴', '鸡', '狗', '猪'];
anotherArr.forEach(item => {setAnother.add(item);
});
console.log(set.union(setAnother).show()); // 牛 虎 兔 龙 蛇 马 羊 猴 鸡 狗 猪
console.log(set.intersect(setAnother).show()); // 马 羊 猴
console.log(set.isSubsetOf(setAnother)); // false
复制代码

散列表/哈希表

散列是一种常用的存储技术,散列使用的数据结构叫做散列表/哈希表。在散列表上插入、删除和取用数据都非常快,但是对于查找操作来说却效率低下,比如查找一组数据中的最大值和最小值。查找的这些操作得求助其它数据结构,比如下面要讲的二叉树。

切入个案例感受下哈希表:

假如一家公司有1000个员工, 现在我们需要将这些员工的信息使用某种数据结构来保存起来。你会采用什么数据结构呢?

  • 方案一:数组

    • 按照顺序将所有员工信息依次存入一个长度为1000的数组中。每个员工的信息都保存在该数组的某个位置上。
    • 但是我们要查看某个员工的信息怎么办呢?一个个查找吗?不太好找。
    • 数组最大的优势是什么?通过下标值获取信息。
    • 所以为了可以通过数组快速定位到某个员工,最好给员工信息中添加一个员工编号,而编号对应的就是员工的下标值
    • 当查找某个员工信息时,通过员工号可以快速定位到员工的信息位置。
  • 方案二:链表

    • 链表对应插入和删除数据有一定的优势。
    • 但是对于获取员工的信息,每次都必须从头遍历到尾,这种方式显然不是特别适合我们这里。
  • 最终方案:

    • 这么看最终方案似乎就是数组了,但是数组还是有缺点,什么缺点呢?
    • 假如我们想查看下张三这位员工的信息,但是我们不知道张三的员工编号,怎么办呢?
    • 当然,我们可以问他的员工编号。但是我们每查找一个员工都是要问一下这个员工的编号吗?不合适。【那我们还不如直接问他的信息嘞】
    • 能不能有一种办法,让张三的名字和他的员工编号产生直接的关系呢?
    • 也就是通过张三这个名字,我们就能获取到他的索引值,而再通过索引值我们就能获取张三的信息呢?
    • 这样的方案已经存在了,就是使用哈希函数,让某个key的信息和索引值对应起来。

那么散列表的原理和实现又是怎样的呢,我们来聊聊。

我们的哈希表是基于数组完成的,我们从数组这里切入解析下。数组可以通过下标直接定位到相应的空间,哈希表的做法就是类似的实现。哈希表把key(键)通过一个固定的算法函数(此函数称为哈希函数/散列函数)转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value(值)存储在以该数字为下标的数组空间里,而当使用哈希表进行查询的时候,就是再次使用哈希函数将key转换为对应的数组下标,并定位到该空间获取value

结合下面的代码,也许你会更容易理解:

// 哈希表
class HashTable {constructor(){this.table = new Array(137);}/*** @method hashFn 哈希函数* @param { String } data 传入的字符串* @return { Number } 返回取余的数字*/hashFn(data){let total = 0;for(let i = 0; i < data.length; i++){total += data.charCodeAt(i);}return total % this.table.length;}/*** * @param { String } data 传入的字符串*/put(data){let pos = this.hashFn(data);this.table[pos] = data;return this;}// 展示show(){this.table && this.table.forEach((item, index) => {if(item != undefined){console.log(index + ' => ' + item);}})}// ...获取值get函数等看官感兴趣的话自己补充测试啦
}let hashtable = new HashTable(),arr = ['mouse', 'ox', 'tiger', 'rabbit', 'dragon', 'snake', 'horse', 'sheep', 'monkey', 'chicken', 'dog', 'pig'];
arr.forEach(item => {hashtable.put(item);
});
hashtable.show();
// 5 => mouse
// 40 => dog
// 46 => pig
// 80 => rabbit
// 87 => dragon
// 94 => ox
// 111 => monkey
// 119 => snake
// 122 => sheep
// 128 => tiger
// 134 => horse// 那么问题来了,十二生肖里面的_小鸡_去哪里了呢❓
// 被_小萌狗_给覆盖了,因为其位置都是40(这个可以自己证明下)
// 问题又来了,那么应该如何解决这种被覆盖的冲突呢❓
复制代码

针对上面的问题,我们存储数据的时候,产生冲突的话我们可以像下面这样解决:

1. 线性探测法

当发生碰撞(冲突)时,线性探测法检查散列表中的下一个位置【有可能非顺序查找位置,不一定是下一个位置】是否为空。如果为空,就将数据存入该位置;如果不为空,则继续检查下一个位置,直到找到一个空的位置为止。该技术是基于一个事实:每个散列表都有很多空的单元格,可以使用它们存储数据。

2. 开链法

但是,当发生碰撞时,我们任然希望将key(键)存储到通过哈希函数产生的索引位置上,那么我们可以使用开链法开链法是指实现哈希表底层的数组中,每个数组元素又是一个新的数据结构,比如另一个数组(这样结合起来就是二位数组了),链表等,这样就能存储多个键了。使用这种技术,即使两个key(键)散列后的值相同,依然是被保存在同样的位置,只不过它们是被保存在另一个数据结构上而已。以另一个数据结构是数组为例,存储的数据如下:

二叉查找树

  • 树的定义:

    • 树(Tree):n(n >= 0)个节点构成的有限集合。

      • n = 0时,称为空树;
      • 对任意一棵空树(n > 0),它具备以下性质:
      • 树中有一个称为**根(Root)**的特殊节点,用r(root)表示;
      • 其余节点可分为m(m > 0)个互不相交的有限集T1,T2,...Tm,其中每个集合本省又是一棵树,称为原来树的子树(SubTree)
    • 注意:

      • 子树之间不可以相交
      • 除了根节点外,每个节点有且仅有一个父节点;
      • 一个N个节点的树有N-1条边。
  • 树的术语:

    • 节点的度(Degree):节点的子树个数。
    • 树的度:树的所有节点中最大的度数(树的度通常为节点个数的N-1)。
    • 叶节点(Leaf):度为0的节点(也称叶子节点)。
    • 父节点(Parent):有子树的节点是其子树的父节点。
    • 子节点(Child):若A节点是B节点的父节点,则称B节点是A节点的子节点。
    • 兄弟节点(Sibling):具有同一个父节点的各节点彼此是兄弟节点。
    • 路径和路径长度:从节点n1nk的路径为一个节点序列n1,n2,n3,...,nknini+1的父节点。路径所包含边的个数为路径长度。
    • 节点的层次(Level):规定根节点在第0层,它的子节点是第1层,子节点的子节点是第2层,以此类推。
    • 树的深度(Depth):树中所有节点中的最大层次是这棵树的深度(因为上面是从第0层开始,深度 = 第最大层数 + 1)

如下图:

  • 二叉树的定义:

    • 二叉树可以为空,也就是没有节点
    • 二叉树若不为空,则它是由根节点和称为其左子树TL和右子树RT的两个不相交的二叉树组成
    • 二叉树每个节点的子节点不允许超过两个
  • 二叉树的五种形态:

    • 只有根节点
    • 只有左子树
    • 只有右子树
    • 左右子树均有

对应下图(从左至右):

我们接下来要讲的是二叉查找树(BST,Binary Search Tree)二叉查找树,也称二叉搜索树或二叉排序树,是一种特殊的二叉树,相对值较的值保存在节点中,较的值保存在节点中。二叉查找树特殊的结构使它能够快速的进行查找、插入和删除数据。下面我们来实现下:

// 二叉查找树
// 辅助节点类
class Node {constructor(data, left, right){this.data = data;this.left = left;this.right = right;}// 展示节点信息show(){return this.data;}
}
class BST {constructor(){this.root = null;}// 插入数据insert(data){let n = new Node(data, null, null);if(this.root == null){this.root = n;}else{let current = this.root,parent = null;while(true){parent = current;if(data < current.data){current = current.left;if(current == null){parent.left = n;break;}}else{current = current.right;if(current == null){parent.right = n;break;}}}}return this;}// 中序遍历inOrder(node){if(!(node == null)){this.inOrder(node.left);console.log(node.show());this.inOrder(node.right);}}//   先序遍历preOrder(node){if(!(node == null)){console.log(node.show());this.preOrder(node.left);this.preOrder(node.right);}}// 后序遍历postOrder(node){if(!(node == null)){this.postOrder(node.left);this.postOrder(node.right);console.log(node.show());}}// 获取最小值getMin(){let current = this.root;while(!(current.left == null)){current = current.left;}return current.data;}// 获取最大值getMax(){let current = this.root;while(!(current.right == null)){current = current.right;}return current.data;}// 查找给定的值find(data){let current = this.root;while(current != null){if(current.data == data){return current;}else if(data < current.data){current = current.left;}else{current = current.right;}}return null;}// 移除给定的值remove(data){root = this.removeNode(this.root, data);return this;}// 移除给定值的辅助函数removeNode(node, data){if(node == null){return null;}if(data == node.data){// 叶子节点if(node.left == null && node.right == null){return null; // 此节点置空}// 没有左子树if(node.left == null){return node.right;}// 没有右子树if(node.right == null){return node.left;}// 有两个子节点的情况let tempNode = this.getSmallest(node.right); // 获取右子树node.data = tempNode.data; // 将其右子树的最小值赋值给删除的那个节点值node.right = this.removeNode(node.right, tempNode.data); // 删除指定节点的下的最小值,也就是置其为空return node;}else if(data < node.data){node.left = this.removeNode(node.left, data);return node;}else{node.right = this.removeNode(node.right, data);return node;}}// 获取给定节点下的二叉树最小值的辅助函数getSmallest(node){if(node.left == null){return node;}else{return this.getSmallest(node.left);}}
}let bst = new BST();
bst.insert(56).insert(22).insert(10).insert(30).insert(81).insert(77).insert(92);
bst.inOrder(bst.root); // 10, 22, 30, 56, 77, 81, 92
console.log('--中序和先序遍历分割线--');
bst.preOrder(bst.root); // 56, 22, 10, 30, 81, 77, 92
console.log('--先序和后序遍历分割线--');
bst.postOrder(bst.root); // 10, 30, 22, 77, 92, 81, 56
console.log('--后序遍历和获取最小值分割线--');
console.log(bst.getMin()); // 10
console.log(bst.getMax()); // 92
console.log(bst.find(22)); // Node { data: 22, left: Node { data: 10, left: null, right: null }, right: Node { data: 30, left: null, right: null } }
// 我们删除节点值为22,然后用先序的方法遍历,如下
console.log('--移除22的分割线--')
console.log(bst.remove(22).inOrder(bst.root)); // 10, 30, 56, 77, 81, 92
复制代码

看了上面的代码之后,你是否有些懵圈呢?我们借助几张图来了解下,或许你就豁然开朗了。

在遍历的时候,我们分为三种遍历方法--先序遍历,中序遍历和后序遍历:

删除节点是一个比较复杂的操作,考虑的情况比较多:

  • 该节点没有叶子节点的时候,直接将该节点置空;
  • 该节点只有左子树,直接将该节点赋予左子树
  • 该节点只有右子树,直接将该节点赋予右子树
  • 该节点左右子树都有,有两种方法可以处理
    • 方案一:从待删除节点的子树找节点值最大的节点A,替换待删除节点值,并删除节点A
    • 方案二:从待删除节点的子树找节点值最小的节点A,替换待删除节点值,并删除节点A【?上面的示例代码中就是这种方案】

删除两个节点的图解如下:

由边的集合及顶点的集合组成。

我们来了解下图的相关术语:

  • 顶点:图中的一个节点。
  • 边:表示顶点和顶点之间的连线。
  • 相邻顶点:由一条边连接在一起的顶点称为相邻顶点。
  • 度:一个顶点的度是相邻顶点的数量。比如0顶点和其它两个顶点相连,0顶点的度就是2
  • 路径:路径是顶点v1,v2...,vn的一个连续序列。
    • 简单路径:简单路径要求不包含重复的顶点。
    • 回路:第一个顶点和最后一个顶点相同的路径称为回路。
  • 有向图和无向图
    • 有向图表示图中的方向的。
    • 无向图表示图中的方向的。
  • 带权图和无权图
    • 带权图表示图中的边有权重
    • 无权图表示图中的边无权重

如下图:

图可以用于现实中的很多系统建模,比如:

  • 对交通流量建模
    • 顶点可以表示街道的十字路口, 边可以表示街道.
    • 加权的边可以表示限速或者车道的数量或者街道的距离.
    • 建模人员可以用这个系统来判定最佳路线以及最可能堵车的街道.

图既然这么方便,我们来用代码实现下:

// 图
class Graph{constructor(v){this.vertices = v; // 顶点个数this.edges = 0; // 边的个数this.adj = []; // 邻接表或邻接表数组this.marked = []; // 存储顶点是否被访问过的标识this.init();}init(){for(let i = 0; i < this.vertices; i++){this.adj[i] = [];this.marked[i] = false;}}// 添加边addEdge(v, w){this.adj[v].push(w);this.adj[w].push(v);this.edges++;return this;}// 展示图showGraph(){for(let i = 0; i < this.vertices; i++){for(let j = 0; j < this.vertices; j++){if(this.adj[i][j] != undefined){console.log(i +' => ' + this.adj[i][j]);}}}}// 深度优先搜索dfs(v){this.marked[v] = true;if(this.adj[v] != undefined){console.log("visited vertex: " + v);}this.adj[v].forEach(w => {if(!this.marked[w]){this.dfs(w);}})}// 广度优先搜索bfs(v){let queue = [];this.marked[v] = true;queue.push(v); // 添加到队尾while(queue.length > 0){let v = queue.shift(); // 从对首移除if(v != undefined){console.log("visited vertex: " + v);}this.adj[v].forEach(w => {if(!this.marked[w]){this.marked[w] = true;queue.push(w);}})}}
}let graphFirstInstance = new Graph(5);
graphFirstInstance.addEdge(0, 1).addEdge(0, 2).addEdge(1, 3).addEdge(2, 4);
graphFirstInstance.showGraph();
// 0 => 1
// 0 => 2
// 1 => 0
// 1 => 3
// 2 => 0
// 2 => 4
// 3 => 1
// 4 => 2
// ❓为什么会出现这种数据呢?它对应的图是什么呢?可以思考?下,动手画画图什么的
console.log('--展示图和深度优先搜索的分隔线--');
graphFirstInstance.dfs(0); // 从顶点 0 开始的深度搜索
// visited vertex: 0
// visited vertex: 1
// visited vertex: 3
// visited vertex: 2
// visited vertex: 4
console.log('--深度优先搜索和广度优先搜索的分隔线--');
let graphSecondInstance = new Graph(5);
graphSecondInstance.addEdge(0, 1).addEdge(0, 2).addEdge(1, 3).addEdge(2, 4);
graphSecondInstance.bfs(0); // 从顶点 0 开始的广度搜索
// visited vertex: 0
// visited vertex: 1
// visited vertex: 2
// visited vertex: 3
// visited vertex: 4
复制代码

对于搜索图,在上面我们介绍了深度优先搜索 - DFS(Depth First Search)广度优先搜索 - BFS(Breadth First Search),结合下面的图再回头看下上面的代码,你会更加容易理解这两种搜索图的方式。

后话

文章中的一些案例来自coderwhy的数据结构和算法系列文章,感谢其授权

绘图软件 Numbers,本篇文章用到的图片绘图稿感兴趣可以下载。

演示代码存放地址 -- 数据结构文件夹 进入structure目录可以直接 node + filename 运行

文章首发 github.com/reng99/blog…

更多内容 github.com/reng99/blog…

参考

  • coderwhy的数据结构和算法系列文章

  • 《数据结构与算法JavaScript描述》

转载于:https://juejin.im/post/5c98b7215188252da8737c33

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. w3school和w3cschool两个网站有什么关系和区别?(转)

    w3school地址:http://www.w3school.com.cn/ w3cschool地址:http://www.w3cschool.cn/总结结论:w3更早。w3school是06年注册的,而w3cschool是09年注册的。 w3c更权威。其中w3cschool是应w3c中国组织官方而出的,而w3school是上海赢科投资有限公司进行投资的,但是是为了协助…...

    2024/4/14 22:50:39
  2. 【无法访问此网站localhost 拒绝了我们的连接请求。请试试以下办法:检查网络连接检查代理服务器和防火墙ERR_CONNECTION_REFUSED】出现以上错误

    【无法访问此网站localhost 拒绝了我们的连接请求。请试试以下办法:检查网络连接检查代理服务器和防火墙ERR_CONNECTION_REFUSED】出现以上错误 打开eclipse以后最下面有个“tomcat v8.5 Servers"点一下,然后点击下图右边绿色启动按钮,tomcat的localhost就打开了...

    2024/4/18 8:07:06
  3. 关于eclipse安装PyDev后preference下始终没有Pydev选项的解决方案

    1,下载最新版的eclipse(因为很多插件都是和版本相关的,所以最新的支持的插件最多) 点击云盘链接: http://pan.baidu.com/s/1o8etpbG2下载项目要求的JDK和最新版的Pydev环境(JDK如果大家找不到Pydev的环境可以点击下面的链接,这个版本是自动配置环境变量的,不像免安装版…...

    2024/5/3 1:25:47
  4. 18年怎么将win7升级到win10教程

    1.下载升级工具(MediaCreationTool.exe)地址还是是微软官方地址:http://t.cn/RL9FHu02. 使用MediaCreationTool.exe升级系统打开MediaCreationTool.exe,若弹错请关闭第三方安全或优化软件,然后多尝试几次。3. 然后就是按照MediaCreationTool.exe这个软件打开的安装界面安…...

    2024/4/16 5:49:44
  5. 文件共享 无法访问,你可能没有权限使用网络资源,请与这台服务器的管理员联...

    文件或文件夹设置成共享之后,通过"运行"(如运行---->\\192.168.0.123)进行访问,报错:\\192.168.0.123无法访问,你可能没有权限使用网络资源,请与这台服务器的管理员联系。这问题碰到好几回了,每次都是同事帮忙解决的,这次又碰到了同事不在,只能自己来。 (…...

    2024/4/19 23:22:51
  6. w3school和w3cschool两个网站有什么关系和区别?

    w3school地址:http://www.w3school.com.cn/ w3cschool地址:https://www.w3cschool.cn其中w3cschool是应w3c中国组织官方而出的,而w3school是上海赢科投资有限公司进行投资的,但是是为了协助w3c中国组织而出的重点大家应该也看出来了,就在这个“协助”二字。结果不言而喻,…...

    2024/4/18 21:36:37
  7. qq微博 PHP SDK

    之前未做过PHP开发,下载了一个LAMP的免安装包,放入PHP的脚本页面,启动以后,发现有错误; 查找了半天,发现只需要把$PHP/php.ini中的一行注释,打开即可;extension=php_openssl.dll >> extension=php_openssl.dllWarning : fsockopen() [function.fsockopen ]: una…...

    2024/4/29 0:10:27
  8. win7下mini2440 USB下载驱动安装

    首先说一下我的系统环境:win7 旗舰版 32位,下面是具体的过程。 1、下载驱动:http://download.csdn.net/detail/zxj2018/4472473 2、安装LibUsbDotNet_Setup.2.2.7.exe程序,(请无视ReadMe-Install.txt提示的安装MINI2440.inf步骤) 3、连好串口、USB下载线,以Nor Flash方式…...

    2024/4/14 21:55:59
  9. W3school和W3Cschool的区别

    前言 相信初学者可能会误以为这两个网站是同一个网站,其实不然。 区别他两最大的区别就是后者比前者多了一个’C’咳咳,开个玩笑。 首先我们来看这两个网站的官方介绍W3scool关于 W3School 下面是关于 W3School 的简要描述: W3School 是因特网上最大的 WEB 开发者资源 W3Sch…...

    2024/4/14 21:55:58
  10. 汇编语言实验十进制2进制16进制转换输出

    从键盘输入一个0–255之间的整数,在屏幕上显示出该整数对应的二进制和十六进制数。DATA SEGMENT MEG DB Please input a number(0~255):,0DH,0AH,$ ME2 DB 0DH,0AH,Input a invalid number,exit!,0DH,0AH,$ X DB ? Y DB 0AH BUF DB 10DB ?DB 10 DUP(?) BU2 DB 0,$DB 8 DUP(…...

    2024/4/19 19:10:37
  11. 剑指Offer SnakeNumber 蛇形填数

    题目描述: 在n*n的方阵里填入1,2..,n*n 要求填成蛇形例如n=4时方阵为:10 11 12 1 9 16 13 2 8 15 14 3 7 6 5 4思路: 首先蛇形填入数据,可以把操作分为四个步骤:从上往下,从右往左,从下往上,从左往右。 定义x,y表示元素在矩阵中的横纵坐标,首先循环从上往下给数组…...

    2024/5/2 23:49:39
  12. Win7与Linux双系统引导修复

    此文是本人经历与网上资料整理如下,便与大家借鉴我们遇到引导问题的原因可能有 1. 删除Linux,直接在win下删了该分区 2. 调整磁盘,利用工具合并,删除,分割分区导致磁盘分区数目变化 3. 重新安装系统,把Linux下安到新分区,原分区格式化,但没有重装grub2 …...

    2024/4/14 21:55:58
  13. 易语言5.9 免安装直用文件(非官方)

    链接:https://pan.baidu.com/s/1dH647FxeENTnGgAdIsTsHA 提取码:npem 注意事项: 1.此产品无需下载,解压后可直接使用。 2.若无法使用,请运行官方安装程序(见文件夹内)进行安装。 3.若官方安装程序安装后仍无法使用,请进QQ群(201853910)内,将有人员提供帮助。 4.此文…...

    2024/4/14 21:55:54
  14. vb获得网络时间的函数(历史上最全最完整最可靠的函数)

    一般获取网络时间的方法都是去找到个可以显示时间的网址,这里列几个比较可靠的: http://www.beijing-time.org/time15.asp http://api.k780.com:88/?app=life.time&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json不过这些可能会随着人家网址变…...

    2024/4/14 21:55:53
  15. Win7安装ADB连接安卓手机

    1、下载adb,地址: http://www.androiddevtools.cn/ 2、下载 3、解压到你想放的目录 4、配置环境变量,新增系统变量android,值为解压目录下的platform-tools目录 5、系统变量path中增加%android%; 6、cmd中运行adb,显示如下则表示配置成功 7、安卓手机root,使用usb连接…...

    2024/4/14 21:55:53
  16. MySQL的下载安装及eclipse配置+jdbc

    引述 文章借鉴了百度经验和其他博客,可以说是对所有文章的做了一个自我总结,一些麻烦的地方和容易出现错误的地方遇到的一起解决MySQL官网分为免安装版和安装版,由于免安装版5.7.20以后都不附带data文件夹及my.ini文件,需要自己手动创建,相当麻烦,在这里我们下载安装版,…...

    2024/4/18 21:45:27
  17. QT版本的人工智能贪食蛇SNAKE AI编程实践

    1、概述在2020年3月份的某一天,脑子里突然闪现当年Nokia手机上的经典贪食蛇游戏来,通过上、下、左、右四个按键控制贪食蛇的前进方向,在保证贪食蛇不碰撞到边界和自身身体的前提下,吃到更多的食物,从而得到更高的分数。于是,很自然地就想到是否可以引入一套智能算法,让计…...

    2024/4/18 8:36:30
  18. C++任意数字类型转 2进制、8进制、16进制

    C++任意数字类型转 2进制、8进制、16进制 平时我们在写程序的过程中会经常碰见进制转换的操作,偶尔写一次还好每次写,我们都又要重新定义函数进行转换;在这里博主就分享一下我自己编写的一个进制转换的方法吧,也比较通用; (如没有耐心可以直接跳过思路 看尾部的源代码) …...

    2024/4/19 20:28:29
  19. Shiro-菜鸟初试篇

    Shiro 框架介绍 Subject(主体) shiro中是一个接口,接口中定义了很多认证授权相关的方法 通过安全管理器进行认证和授权。 SecuritManager(安全管理器) 它是shiro的核心 负责对所有的subject进行安全管理。 通过SecuritManager可以完成subject的认证、授权等操作。 SecurityMan…...

    2024/4/19 12:21:08
  20. windows 和linux做时间同步方法

    周海汉 /文 2010.6.9此前写了篇《linux设置时间 服务器》,解决了Linux之间时间同步的问题。windows系统想偷懒,用windows自带的internet时间同步,向time.windows.com进行同步。双击任务栏右下角的时间,弹出时间设置,可以选择用哪些时间服务器更新。但如果windows系统不能上…...

    2024/4/14 22:50:33

最新文章

  1. 【Android学习】按钮监听代码

    1. 简介 Button组件是Android中常用的组件&#xff0c;Button常需要和View.OnClickListener配合使用。这里记录下Button配置监听的过程。 2. 代码分析 2.1 Layout的XML代码 <Buttonandroid:id"id/btn"android:layout_width"match_parent"android:lay…...

    2024/5/3 1:27:46
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 【C++】类和对象①(什么是面向对象 | 类的定义 | 类的访问限定符及封装 | 类的作用域和实例化 | 类对象的存储方式 | this指针)

    目录 前言 什么是面向对象&#xff1f; 类的定义 类的访问限定符及封装 访问限定符 封装 类的作用域 类的实例化 类对象的存储方式 this指针 结语 前言 最早的C版本&#xff08;C with classes&#xff09;中&#xff0c;最先加上的就是类的机制&#xff0c;它构成…...

    2024/5/1 13:18:37
  4. 【Godot4自学手册】第三十五节摇杆控制开门

    本节主要实现&#xff0c;在地宫墙壁上安装一扇门&#xff0c;在核实安装一个开门的摇杆&#xff0c;攻击摇杆&#xff0c;打开这扇门&#xff0c;但是只能攻击一次&#xff0c;效果如下&#xff1a; 一、添加完善节点 切换到underground场景&#xff0c;先将TileMap修改一下…...

    2024/5/1 13:37:32
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/1 17:30:59
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/2 16:16:39
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/2 9:28:15
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/2 15:04:34
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/30 22:21:04
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/1 4:32:01
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/2 9:07:46
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57