0
点赞
收藏
分享

微信扫一扫

数据结构:JavaScript语言描述

fbd4ffd0717b 2022-01-26 阅读 59

列表

不需要在一个很长的序列中查找元素或者对其进行排序时。

列表的抽象数据类型定义

列表是一组有序的数据。(是数据本身有序,还是索引有序)
列表中的每个数据项称为元素。列表中可以是任意数据类型。列表中元素的数量受到程序内存的限制。
不包含任何元素的列表称为空列表。

属性

length:列表元素的个数
listSize:保存列表的元素个数

方法

append():可以再列表末尾添加元素,也可以在给定元素后或列表的起始位置insert一个元素
remove():从列表中删除元素。
clear():清除列表中的所有元素。
toString():显示列表中所有的元素。
getElement():显示当前元素。
列表拥有描述元素位置的属性。列表有钱有后。
next():可以从当前元素移动到下一个元素。
prev():可以移动到当前元素的前一个元素。
moveTo():移动到指定位置
currPos():列表中的当前位置
end():将当前元素移动到最后一个位置
front():将当前元素移动到第一个位置

列表的实现

function List() {
    this.dataStore = [];
    this.listSize = 0;
    this.pos = 0;
    this.length = this.dataStore.length;
}

List.prototype.append = function (element) {
    this.dataStore[this.listSize++] = element;
}
List.prototype.remove = function (element) {
    let result = this.dataStore.findIndex((ele)=>{
        return ele === element;
    });
   this.dataStore.splice(result, 1);
   this.listSize--;
   return 1;
}

List.prototype.find = function (element) {
    return this.dataStore.indexOf(element);
}

List.prototype.length = function () {
    return this.listSize;
}

List.prototype.toString = function () {
    return this.dataStore.toString();
}

// 将元素插入到某个元素之后

List.prototype.insert = function (element, after) {
    let index = this.dataStore.findIndex((ele)=>{
        return ele === after
    });

    if(index > -1) {
        this.dataStore.splice(index + 1, 0 ,element);
        this.listSize++; 
        return true;
    }

    return false;
   
}   

List.prototype.clear = function () {
    delete this.dataStore;
    this.dataStore.length = 0;
    this.listSize = 0;
    this.pos = 0;
}

List.prototype.contains = function (element) {
    return this.dataStore.includes(element);
}

List.prototype.front = function () {
    this.pos = 0;
}

List.prototype.end = function () {
    this.pos = this.listSize-1;
}

List.prototype.getElement = function () {
    return this.dataStore[this.pos];
}

List.prototype.prev = function () {
    if(this.pos > 0){
        this.pos--;
    }
}

List.prototype.next = function () {
    if(this.pos < this.listSize-1){
        this.pos++;
    }
}
// 返回当前位置
List.prototype.currPos = function () {
    return this.pos;
}
// 移动到指定位置
List.prototype.moveTo = function (index) {
    this.pos = index;
}

使用迭代器遍历列表

使用迭代器的优点

  1. 不必关心底层的数据存储结构
  2. 当向列表中插入数据时,只更新列表,不需要更新迭代器
  3. 迭代器为访问列表提供了统一的方式
// 从前往后遍历
for(list.front(); list.curPos() < list.length(); list.next()){
	console.log(list.getElement())
}

// 从后往前遍历
for(list.end(); list.curPos() >= 0; list.prev()){
	console.log(list.getElement());
}

对栈的操作

栈是一种特殊的列表。栈只能在列表的一端进行,这一端称为栈顶。栈中的数据先入后出。

属性

top:栈顶的位置
length:记录栈内元素的个数

方法

push():压入栈
pop():弹出栈
peak():返回栈顶元素
clear():清除所有元素

栈的实现

function Stack() {
    this.stack = [];
    this.top = 0;
    this.length = 0;
}

Stack.prototype.push = function (element) {
    this.stack[this.top] = element;
    this.top++;
}

Stack.prototype.pop = function () {
    this.stack.splice(this.top - 1, 1);
    this.top--;
}

Stack.prototype.clear = function () {
  this.top = 0;
}

Stack.prototype.peak = function () {
    return this.stack[this.top - 1];
} 

Stack.prototype.length = function () {
    return this.top;
}

队列

队列是一种列表,对只能在队尾插入,队首删除。队列用于存储按顺序排列的数据,先入先出。

对队列的操作

插入的操叫作入队,删除的操作叫出对。

方法

peak():读取队首的元素
clear():清空队列
enquenen():入队
dequenen():出队
empty():判断队列是否为空
front():读取队列的第一个元素
end():读取队列的最后一个元素

队列的实现

function Quenen() {
    this.dataStore = [];
    
}
// 入队
Quenen.prototype.enquenen = function (element) {
    this.dataStore.push(element);

}
// 出队
Quenen.prototype.dequenen = function () {
    this.dataStore.shift();

}
// 清除队列
Quenen.prototype.clear = function () {
    this.dataStore = [];
}

Quenen.prototype.toString = function () {
    return this.dataStore.toString();
}
// 判断是否为空对列
Quenen.prototype.empty = function () {
    if(this.dataStore.length === 0){
        return true;
    } else {
        return false;
    }
}
// 返回队首的元素
Quenen.prototype.front = function () {
    return this.dataStore[0];
}
// 返回队尾的元素
Quenen.prototype.end = function () {
    return this.dataStore[this.dataStore.length - 1];
}

链表

数组的缺点

  1. 数组的长度是固定的,当数组被填满时再添加会很困难
  2. 添加和删除元素很麻烦,需要将元素前移或后移
  3. 速度很慢

定义链表

链表是由一组节点组成的集合。每个节点都使用 一个对象的引用指向它的后继,指向另一个节点的引用叫作链。
数组元素靠他们的位置进行引用,链表则靠它们的相互关系进行引用。链表的尾元素指向一个null。
在链表的最前面有一个头结点。
添加元素:当前节点的前驱指向新节点,新节点指向原来前驱指向的节点。
删除元素:被删除元素的前驱指向被删除节点指向的节点,被删除节点指向null。

操作

方法

insert():在某个节点的后面插入
remove():根据节点的内容删除某个节点
findNode():根据节点的内容查找某个节点

链表的实现

function Node(element) {
    this.next = null;
    this.element = element;
}

function LinkedList() {
    this.head = new Node('head');
}

LinkedList.prototype.findNode = function (element) {
    let head = this.head;
    while(head){
        if(head.element === element){
            return head.next;
        }
        head = head.next;
    }
    return head;
}

LinkedList.prototype.findPrevNode = function (element) {
    let head = this.head;
    let currentNode = this.findNode(element);
    while(head.next){
        if(head.next.element === currentNode.element){
            return head;
        }
    }
    return null;
}

// 在节点的后面插入
LinkedList.prototype.insert = function (element, afterElement) {
    let node = new Node(element);
// 先找到节点
    let currentNode = this.findNode(afterElement);
    if(currentNode){
        if(currentNode.next){
            node.next = currentNode.next;
            currentNode.next = ndoe;
        } else {
            currentNode.next = node;
            node.next = null;
        }
    }
    
}

LinkedList.prototype.remove = function (element) {
    let currentNode = this.findNode(element);
    let prevNode = this.findPrevNode(element);
    if(prevNode){
        prevNode.next = currentNode.next;
        currentNode.next = null;
    }
}

双向链表

function Node(element) {
    this.element = element;
    this.previous = null;
    this.next = null;
}
function LLinkedList(){
    this.head = new Node('head');
    this.next = null;
    this.previous = null;
}

LLinkedList.prototype.findNode = function (element) {
    let head = this.head;
    while(head){
        if(head.element === element){
            return head;
        }
        head = head.next;
    }
    return head;
}


LLinkedList.prototype.insert = function (element, afterElement) {
    let node = new Node(element);
    let currentNode = this.findNode(afterElement);
    if(currentNode){
        if(currentNode.next){
            node.previous = currentNode;
            node.next = node.previous.next;
            node.next.previous = node;
            node.previous.next = node;
        } else {
            node.next = null;
            node.previous = currentNode;
            currentNode.next = node;
        }
    }
   
}

LLinkedList.prototype.remove = function (element) {
    let currentNode = this.findNode(element);
    currentNode.previous.next = currentNode.next;
    currentNode.next.previous = currentNode.previous;
    currentNode.next = null;
    currentNode.previous = null;
}

循环链表

function Node(element) {
    this.element = element;
    this.next = null;
}

function CycleLinkedList() {
    this.head = new Node('head');
    this.head.next = this.head;
}

CycleLinkedList.prototype.findNode = function (element) {
    let head = this.head;
    while(head){
        if(head.element === element){
            return head;
        }
        head = head.next;
    }
    return head;
}

CycleLinkedList.prototype.insert = function (element, afterElement) {
    let node = new Node(element);
    let currentNode = this.findNode(afterElement);
    if(currentNode){
        if(currentNode.next){
            node.next = currentNode.next;
            currentNode.next = node;
        } else {
            currentNode.next = node;
            node.next = null;
        }
    }
}

字典

字典是一种以键值对形式存储数据的数据结构。字典类的基础是Array类,而不是Object类。

字典的实现

function Dictionary()  {
    this.dataStore =  new Array();
}

Dictionary.prototype.add = function (key,value) {
    this.dataStore[key] = value;
}

Dictionary.prototype.find = function (key) {
    return this.dataStore[key];
}

Dictionary.prototype.remove = function (key) {
    delete this.dataStore[key];
}

Dictionary.prototype.showAll = function () {
    for(let key in Object.keys(this.dataStore).sort()){
        console.log(key + '->' + this.dataStore[key]);
    }
}

Dictionary.prototype.count = function () {
    let n = 0;
    for(let key in Object.keys(this.dataStore)){
        ++n;
    }
    return n;
}

散列

散列后的数据可以快速存取。散列使用的数据结构是散列表。插入、删除和取用速度快,但是查找速度慢。
通过散列表存储数据时,通过散列函数将键映射为一个数字,数字的范围是0到散列表的长度。
当两个键映射为一个值的情况叫作碰撞。
散列表的数组长度应该是一个质数。

选择散列函数

散列函数的选择取决于键值的数据类型。除留余数法。如果键的类型是字符串将各个字符的ASCII的值的和除以数组的长度的余数。

碰撞处理

开链法

实现散列表的底层数组中,每个元素又是另一个数据结构

线性检测法

当遇到碰撞时,检测散列表的下一个的位置是否为空,如果为空则将数据存入该位置,如果不为空则继续检测下一个位置,直到找到一个空的位置为止。当存储数据的数组特别大时,使用线性检测法比开链法好。
如果数组的大小是待存储数据个数的1.5倍时,使用开链法;如果数组的大小是待存储数据个数的2倍及以上使用线性检测法。

集合

集合是一种包含不同元素的数据结构。集合中的元素称为成员。集合中的成员是无序的,集合中不允许相同元素存在。
集合是由一组无序但是彼此之间有相关性的成员构成的,每个成员在集合中只能出现一次。

集合的定义

  • 不包含任何成员的集合称为空集,全集则是包含一切可能成员的集合。
  • 如果两个集合的成员完全相同,则称这两个集合相等。
  • 如果一个集合中的所有元素是全部包含于另一个集合,那么前一个集合是后一个集合的子集。

对集合的操作

  1. 并集:将两个集合的成员进行合并,形成一个集合
  2. 交集:两个集合中共同存在的成员组成一个新的集合
  3. 补集:属于一个集合而不属于另一个集合的成员组成的集合

方法

  • add:向集合中添加成员
  • remove:从集合中删除成员
  • show:显示集合中的成员

set类的实现

function Set(){
    this.dataStore = [];
    this.size = 0;
}

// 工具函数
Set.prototype.indexOf = function (element) {
    return this.dataStore.indexOf(element);
} 

Set.prototype.add = function (element) {
    if(this.indexOf(element) !== -1){
        return false;
    } else {
        this.dataStore.push(element);
    }
}

Set.prototype.remove = function (element) {
    let index = this.indexOf(element);
    if(index !== -1){
        this.dataStore.splice(index, 1);
    } else {
        return false;
    }
}

Set.prototype.show = function () {
    return this.dataStore.toString();
}

// 交集
Set.prototype.union = function (set) {
    let unionSet = new Set();
    let tempStore = set.dataStore;
    for(let i = 0; i < this.dataStore.length; i++){
        unionSet.dataStore.concat(set.dataStore.filter((ele) => {
            return ele === this.dataStore[i];
        }));
    }
    return unionSet;
}

// 并集
Set.prototype.interset = function (set) {
    let intersetSet = new Set();
    let tempStore = set.dataStore;
    for(let i = 0; i < this.dataStore.length; i++){
        tempStore = tempStore.filter((ele) => {
            return ele !== this.dataStore[i];
        });
    }

   intersetSet.dataStore = intersetSet.dataStore.concat(this.dataStore, tempStore);  
   return intersetSet;
}

// 补集
Set.prototype.diference = function (set){
    let difSet = new Set();
    for(let i = 0; i < this.dataStore.length; i++){
        if(set.dataStore.every((ele) => {
            return ele !== this.dataStore[i];
        })){
            difSet.dataStore.push(this.dataStore[i]);
        }
    }
    return difSet;
}

树是一种非线性检索的数据结构,以分层的方式存储数据。树被用来存储具有层级关系的数据。树还被用来存储有序列表。

树的定义

树是由以边连接的节点组成。一棵树最上面的节点成为根节点,如果一个节点连接多个节点,称为父节点,它下面的节点成为子节点。一个节点可以有0、1或多个子节点,没有任何子节点的节点成为叶子结点。
二叉树的节点的子节点的数量不超过两个。从一个节点到另一个节点的一组边称为路径。以某种特定顺序访问所有节点叫做树的遍历。树可以分为几个层次,根节点为0层,树中任何一层节点可以看做是子树的根。树的层数就是树的深度。每个节点都有与之相关的值,通常成为键。
一个父节点的两个节点分别成为左节点和右节点。在一些二叉树的实现中,左节点包含一组特定的值,右节点包含一组特定的值。
二叉查找树是一种特殊的二叉树,它的较小的值放在左节点中,较大的值保存在右节点中。

二叉查找树的遍历

中序遍历

以节点的键值,以升序访问树上的节点。
在这里插入图片描述

先序遍历

先访问根节点,再以同样的方法遍历左子树和右子树。
在这里插入图片描述

后序遍历

先访问叶子结点,从左子树到右子树再到根节点。
在这里插入图片描述

删除节点

删除节点首先判断当前节点是否包含待删除数据,如果包含则删除该节点;如果不包含,则比较当前节点的数据和待删除节点的数据,如果当前节点上的数据小于待删除节点的数据则移至当前节点的左节点继续比较;如果当前节点上的数据大于待删除节点的数据则移至当前节点的右节点继续比较。
如果删除的节点是叶子节点,则将从父节点指向他的链接指向null。
如果删除的节点只包含一个子节点,那么原本指向他的链接,指向它的子节点。
如果删除的节点包含两个节点,要么查找待删除节点左子树的最大值,要么查找待删除节点右子树的最小值。

图的定义

图由边的集合与顶点的集合组成。如果一个图的定点对是有序的则称为有向图。对有向图的顶点对排序后,可以在两个顶点之间绘制一个箭头。有向图表明了顶点的流向。
如果图是无序的则称为无序图或无向图。图中的一系列顶点构成路径,路径中的顶点都由边连接。路径的长度用第一个顶点到最后一个顶点之间边的数量表示。
由指向自身的顶点组成的路径称为环,环的长度为0。
圈是至少有一条边的路径,且路径的第一个顶点和最后一个顶点相同。没有重复顶点和重复边的圈就是一个简单的圈。除了第一个和最后一个顶点外,路径中有其他重复的顶点的圈称为平凡圈。
如果两个顶点之间有路径连通,那么这两个顶点称为强连通。
如果有向图的顶点都是强连通的那么这个图也是强连通的。

用图对现实中的系统建模

表示边

我们将表示图的边的方法称为邻接表或邻接表数组。将边表示为有顶点的相邻顶点列表构成的数组,并以此顶点作为索引。
邻接矩阵,是一个二维数组,其中的元素表示两个顶点间是否有一条边。
用一个长度与顶点数量相同的数组记录顶点,为每个元素创造一个子数组,记录相邻顶点的数量。

function Map(v) {
    this.vertices = v;// 结点的总数量
    this.edges = 0;
    this.adj = [];
    this.edgeTo = [];
    this.marked = [];
    for(let i = 0; i < this.vertices; i++){
        this.adj[i] = [];
        this.adj[i].push('');
    }
    for(let i = 0; i < this.vertices; i++){
        this.marked[i] = false;
    }
    
}

Map.prototype.addEdge = function (v,w){
    this.adj[v].push(w);
    this.adj[w].push(v);
    this.edges++;
}

Map.prototype.show = function () {
    for(let i = 0; i < this.vertices; i++){
        for(let j = 0; j < this.vertices; j++){
            if(this.adj[i][j] !== undefined){
                console.log(this.adj[i][j])
            }
        }
    }
}
Map.prototype.pathTo = function (v) {
    var source = 0;
    if (!this.hasPathTo(v)) {
        return undefined;
    }
    var path = [];
    for (var i = v; i != source; i = this.edgeTo[i]) {
        path.push(i);
    }
    path.push(s);
    return path;
}
Map.prototype.hasPathTo =  function (v) {
     return this.marked[v];
}

搜索图

从一个指定的顶点可以到达那些顶点。

深度优先搜索

从起始顶点开始追溯,直到到达最后一个顶点,然后回溯,开始追溯下一条路径直到到达最后的顶点,如此往复,直到没有路径为止。

Map.prototype.dfs = function (v) {
    this.marked[v] = true;
    if(this.adj[v] !== undefined){
        for(let key in this.adj[v]){
            if(!this.marked[key]){
                this.dfs[key];
            }
        }
    }
}

广度优先搜索

从第一个顶点开始,尝试访问尽可能靠近它的顶点。首先检查离第一个顶点最近的层,逐渐向下移动到离第一个节点最远的层。
广度搜索使用抽象队列而不是数组对已访问过的顶点进行排序。

Map.prototype.bfs = function (s) {
  let quenen = [];
    this.marked[s] = true;
    quenen.push(s);
    while(quenen.length > 0){
        let v = quenen.shift();
        if(v === undefined){
            console.log(v+'undefined');
        }       
        for(let i in this.adj[v]){
            if(!this.marked[i]){
               this.edgeTo[i] = v;
               this.marked[i] = true;
               quenen.push(i);
            }
        }
    }
}

原理

查找与当前顶点相邻的未访问顶点,将其添加到已访问顶点列表及队列中。
从图中取出下一个顶点,添加到已访问的列表顶点。
将所有与V相邻的未访问顶点添加到队列。

查找最短路径

广度优先搜索最短路径


// bfs 函数
function bfs(s) {
    var queue = [];
    this.marked[s] = true;
    queue.push(s); //添加到队尾
    while (queue.length > 0) {
        var v = queue.shift(); //从队首移除
        if (v == undefined) {
            print("Visisted vertex:  " + v);
        }
        for each(var w in this.adj[v]) {
            if (!this.marked[w]) {
                this.edgeTo[w] = v;
                this.marked[w] = true;
                queue.push(w);
            }
        }
    }
}

拓扑排序

拓扑排序会对有向图的所有顶点进行排序,使有向边从前面的顶点指向后面的顶点。

拓扑排序算法

举报

相关推荐

0 条评论