数据结构笔记

244 阅读5分钟

数据结构(数据与数据之间的关系和结构)

简单地说,数据结构是以某种特定的布局方式存储数据的容器。这种“布局方式”决定了数据结构对于某些操作是高效的,而对于其他操作则是低效的。首先我们需要理解各种数据结构,才能在处理实际问题时选取最合适的数据结构。

常见的数据结构

首先列出一些最常见的数据结构,我们将逐一说明:

  • 队列
  • 链表
  • 嘻哈表

数组

数组是最简单、也是使用最广泛的数据结构。栈、队列等其他数据结构均由数组演变而来。下图是一个包含元素(1,2,3和4)的简单数组,数组长度为4。

每个数据元素都关联一个正数值,我们称之为索引,它表明数组中每个元素所在的位置。大部分语言将初始索引定义为零

数组可分为队列和栈等

队列

队列是遵循先进先出的一种数据结构,在尾部添加新元素,并从顶部移除元素。

普通队列

function Queue() {
    this.items = [];
}
 
Queue.prototype = {
    enqueue: function (element) {
        this.items.push(element);
    },
    dequeue: function () {
        return this.items.shift();
    },
    front: function () {
        return items[0];
    },
    isEmpty: function () {
        return this.items.length === 0;
    },
    clear: function () {
        this.items = [];
    },
    size: function () {
        return this.items.length;
    },
    print: function () {
        console.log(this.items.toString());
    }
};
示例(场景)
  • 餐厅的叫号网页
  • 点击(取号)按钮生成一个号码
  • 点击(叫号)按钮显示(请x号取餐)

代码

  • 首先选择队列queue做为数据结构
  • queue.pus为入队,queue.shift为出队
  • 其他事情好做啦,完整代码

栈是一种后进先出的数据结构,也就是说最新添加的项最早被移出;它是一种运算受限的线性表,只能在表头/栈顶进行插入和删除操作。

栈有栈底和栈顶。 向一个栈插入新元素叫入栈(进栈),就是把新元素放入到栈顶的上面,成为新的栈顶; 从一个栈删除元素叫出栈,就是把栈顶的元素删除掉,相邻的成为新栈顶; 也就是说栈里面的元素的插入和删除操作,只在栈顶进行;

打个比方:一队人马走入了一条死胡同,只有一个入口,无出口,想要出去就只能把尾变成首开始出去

  1. 栈顶是个开口,可以放入元素即push(),移除元素即pop()。
  2. 栈底封闭,不能操作元素。

图示

1128764697-5f224c21309dc.png

栈的方法

1020668291-5f2252371b727.png

js实现栈的方法

//创建一个函数构造器,用来创建对象
var Stack = function(){
  //可以以数组模拟栈,首元素为栈底,尾元素为栈顶
  var items = []
  //入栈 从栈顶进入一个元素
  this.push = function(element){
    return items.push(element)
  }
  //出栈 从栈顶移除一个元素
  this.pop = function(){
    return items.pop()
  }
  //peek 查看栈顶元素
  this.peek = function(){
    return items[items.length - 1]
  }
  //查看栈有多少个元素
  this.size = function(){
    return items.length
  }
  //判断栈是否为空
  this.isEmpty = function(){
    return items.length == 0
  }
  //清空栈
  this.clear = function(){
    items = []
  }
  //查看数组 
  this.getItems = function(){
    return items
  }
}

示例

JS函数调栈 call stack 就是一个栈

假设f1调用了f2,f2调用了f3

那么f3返回的结果到f2 ,f2返回的结果到f1

function f1(){let a=1; return a+f2()}
function f2(){let b=2;return b+f3()}
function f3(){let c=3;return c}
f1()

画出压栈,出栈的便知这是【后继先出】的栈

链表(一个链一个)

概念

链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。每个 元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成。下图展示了链表的结构:

img

相对于传统的数组,链表的一个好处在于,添加或移除元素的时候不需要移动其他元素。然而,链表需要使用指针,因此实现链表时需要额外注意。 数组的另一个细节是可以直接访问任何位置的任何元素,而要想访问链表中间的一个元素,需要从起点(表头)开始迭代列表直到找到所需的元

实际使用

let array = [1,2,3]
array.__proto__ === Array.prototype
 Array.prototype、__proto__ === Object.prototype
从这个角度看对象就是链表

普通链表:

console.log('=====创建链表=====')
const createNode = (value) => {
    return {
        data: value,
        next: null
    }
}
//创建节点
const createList = (value) => {
    return createNode(value)
}
//添加节点
const appList = (list, value) => {
    const node = createNode(value)
    let x = list
    while (x.next) {
        x = x.next
    }
    x.next = node
    return node
}

//删除节点把要删node
//要删除节点的前一个节点 front node
//要上节点的后一个节点after node
//删除节点的思路:前一个节点下一个节点front node.next指向after node节点
//front node.next === after node
const removeList = (list, node) => {
    let x = list
    let p = node
    while (x !== node && x !== null) {
        p = x
        x = x.next
    }
    if (x === null) {
        return x
    } else if (x === p) {
        p = x.next
        return p
    } else {
        p.next = x.next
        return list
    }
}
const froList = (list, fn) => {
    let x = list
    while (x !== null) {
        fn(x)
        x = x.next
    }
}
//获取
const getList = (list, value) => {
    let node = null
    froList(list, (n) => {
        if (n.data === value) {
            node = n
        }
    })
    return node
}
const list = createList(10)
const node3 = list
const node = appList(list, 50)
const node1 = appList(list, 30)
const node2 = appList(list, 60)
console.log(list)
const node5 = removeList(list, node1)
const node6 = removeList(list, node3)
console.log("rmlist")
console.log(node5)
console.log(node6)
froList(list, (x) => {
    console.log(x.data)
})
console.log(getList(list, 50))

双向链表(每一个节点的prototype都指向上一个节点)

img

class Node {
    constructor(element) {
        this.element = element;
        this.prev = null;
        this.next = null;
    }
}
 
// 双向链表
class DoubleLinkedList {
    constructor() {
        this.head = null;
        this.tail = null;
        this.length = 0;
    }
 
    // 任意位置插入元素
    insert(position, element) {
        if (position >= 0 && position <= ehis.length) {
            let node = new Node(element);
            let current = this.head;
            let previous = null;
            this.index = 0;
            // 首位
            if (position === 0) {
                if (!head) {
                    this.head = node;
                    this.tail = node;
                } else {
                    node.next = current;
                    this.head = node;
                    current.prev = node;
                }
            } else if (position === this.length) { // 末尾
                current = this.tail;
                current.next = node;
                node.prev = current;
                this.tail = node;
            } else {  // 中间
                while(index++ < position) {
                    previous = current;
                    current = current.next;
                }
                node.next = current;
                previous.next = node;
                current.prev = node;
                node.prev = previous;
            }
            this.length++;
            return true;
        }
        return false;
    }
    // 移除指定位置元素
    removeAt(position) {
        if (position > -1 && position < this.length) {
            let current = this.head;
            let previous = null;
            let index = 0;
 
            // 首位
            if (position === 0) {
                this.head = this.head.next
                this.head.prev = null
                if (this.length === 1) {
                    this.tail = null
                }
            } else if (position === this.length - 1) { // 末位
                this.tail = this.tail.prev
                this.tail.next = null
            } else { // 中位
                while (index++ < position) {
                     previous = current
                     current = current.next
                }
                previous.next = current.next
                current.next.prev = previous
            }
            this.length--;
            return current.element;
        } else {
            return null;
        }
    }
 
    // 其他方法
}

循环链表 (最后一个节点指向头节点)

如图所示

img

img

哈希表概念

1.哈希化:将大数字转化为数组范围内下表的过程,我们称之为哈希化。(对大数字取余)

2.哈希函数:通常我们会将单词转化成大数字,大数字在进行哈希化的代码实现放在一个函数中,这个韩式称为哈希函数。

3.哈希表:最终的数据插入到的这个数组,对整个结构的封装,我们称之为是一个哈希表。

数组的缺点

  • 数组进行插入操作时,效率比较低。

  • 数组基于索引去查找的操作效率非常高,基于内容去查找效率很低。

  • 数组进行删除操作,效率也不高。

.哈希表是基于 数组 实现的,但相对于数组有很多优势

1.它可以提供非常快速的 插入-删除-查找 操作

2.无论多少数据,插入和删除需要接近常量的时间。即O(1)的时间级

3.哈希表的速度比树还要快,基本可以瞬间找到想要的元素。

4.哈希表相对于树来说编码要容易

.哈希表对于数组的一些不足

1.哈希表中的数据是没有顺序的,所以不能以一种固定的方式来遍历其中的元素。

2.通常情况下,哈希表中的key是不允许重复的,不能放置相同的key,用于保存不同的元素。

.哈希表的实质

1.哈希表不同于(数组和链表,甚至于树可以画出他的结构)。

2.他的结构就是数组,但他神奇的地方在于它对下标值的一种变换,这种变换称为 哈希函数 , 通过哈希函数可以获取到 HashCode。

一个 哈希表(hash table 或hash map) 是一种实现 关联数组(associative array) 的抽象数据;类型, 该结构可以将 键映射到值

img

树(一个链多个)

树(英语:tree)是一种抽象数据类型或是实现这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。它是由n(n>0)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点

①每个节点有零个或多个子节点; ②没有父节点的节点称为根节点; ③每一个非根节点有且只有一个父节点; ④除了根节点外,每个子节点可以分为多个不相交的子树;

img

树的实现

const shu = (value) => {
    return {
        data: value,
        children: null,
        parent: null
    }
}

const childNode = (node, value) => {
    const newNode = {
        data: value,
        children: null,
        parent: node
    }
    node.children = node.children || []
    node.children.push(newNode)
    return newNode
}
//所有攻击点
const forNode = (node, fn) => {
    fn(node)
    if (!node.children) return
    for (let i = 0; i < node.children.length; i++) {
        forNode(node.children[i], fn)

    }
}

const findNode = (shu, node) => {
    if (shu === node) return shu
    if (shu.children) {
        for (let i = 0; i < shu.children.length; i++) {
            if (shu.children[i] === node) {
                return shu.children[i]
            }
        }
        return undefined
    } else {
        return undefined
    }
}
//删除节点
//找到父节点的所有节点siblings
//找到要删除节点的下标
//根据下标删除节点
const reMode = (shu, node) => {
    const siblings = node.parent.children
    let index = 0
    for (let i = 0; i < siblings.length; i++) {
        if (siblings[i] === node) {
            index = i
        }
    }
    siblings.splice(index, 1)
}
const shu1 = shu(20)
const node = childNode(shu1, 10)
console.log(node)
const node2 = childNode(shu1, 20)
childNode(shu1, 30)
childNode(node, 40)
childNode(node, 60)
childNode(node, 70)
childNode(node, 80)
// console.log(shu1)

// console.log(shu1)
forNode(shu1, (node) => {
    console.log(node.data)
})
// console.log(findNode(shu1, node2))
console.log("删除前")
console.log(shu1)
reMode(shu1, node)
console.log("删除后")
console.log(shu1)