65.9K
CodeProject 正在变化。 阅读更多。
Home

JavaScript 数据结构

2013年10月15日

CPOL

6分钟阅读

viewsIcon

112455

downloadIcon

913

关于基本数据结构和 JavaScript 的教程。

引言

大家好,本文旨在通过基于数据结构概念的示例来教授 JavaScript。如果出于任何原因您不想阅读或复制代码,我已经提供了 .js 文件供下载。

基础

首先,如果您是 JavaScript 新手,您可能更喜欢从 JavaScript 新手应了解的内容 开始。这不是最新的文章,但我宁愿引用 CodeProject 内部的内容,这样更有可能保持可用。

数据结构

根据维基百科

"在计算机科学中,数据结构是一种在计算机中存储和组织数据的特定方式,以便可以高效地使用它们。"

在本文中,我将使用一些在我看来是最基本的结构。我知道通过数组可以获得更好的性能,但这个练习的目的是练习对代码的理解。

数据结构类型

1) 堆栈 

A stack image

堆栈是一种特殊的数据类型或集合,其中主要操作是添加项目(称为 push)和删除项目(称为 pop)。堆栈实现 LIFO(后进先出)结构,这意味着最后添加到结构中的元素必须是第一个被移除的元素。它将具有以下初始代码:

function Stack(){
    var top = null;
    var count = 0;

    //Returns the number of items in the queue
    this.GetCount = function(){
        return count;
    }

    /* Methods */
} 

我们的堆栈将有四个附加方法:Push(data)Pop()Peek()DisplayAll()。这些将在 this.count 下方的 Stack() 函数内部定义。那么,让我们开始吧

推送 (Push)

A stack image

描述:将指定数据推入(添加)到堆栈中并使其成为顶部节点。它还将堆栈计数增加 1。

this.Push = function (data) {
    //Creates a node containing the data and a reference to the next item, if any.
    var node = {
        data: data,
        next: null
    };

    //links the current node to the top node. If the stack is empty it will have null as reference
    node.next = top;

    //makes the current node as the top node.
    top = node;

    //Increases the count
    count++;
} 

窥视

A stack image

描述:查看堆栈顶部的项目。如果堆栈为空,则返回 null。

this.Peek = function(){
	//If there are no items, returns null. (avoid error)
    if(top === null){
		return null;
	}
	else{
		return top.data;
	}
}

Pop

A stack image

描述:它看起来与 PEEK() 方法非常相似,但它还会删除顶部项目并将计数减 1。

this.Pop = function () {
    //If there are no items, returns null. (avoid error)
    if (top === null) {
        return null;
    }
    else {
        //assigns top to a temp variable
        var out = top;

        //makes the TOP as the next in line
        top = top.next;

        //there still are items on the stack
        if (count > 0) {
            count--;
        }

        //returns the value that was removed
        return out.data;
    }
}

显示所有

描述:将堆栈中的所有数据显示为数组。我选择将其显示为数组,因为我不确定如何显示它(例如:console.logdocument.write 等)。

this.DisplayAll = function(){

    if (top === null) {
        return null;
    }
    else {
        //instantiate an array
        var arr = new Array();
        //creates a node to move through the stack
        var current = top;

        //moves through the stack until it reaches the bottom item
        for (var i = 0; i < count; i++) {
            //assigns the data to the array
            arr[i] = current.data;
            //advances one step
            current = current.next;
        }
        //returns the array
        return arr;
    }
}  
  • PUSH(数据):
  • PEEK():
  • POP():
  • DISPLAYALL():

2) 队列 

A stack image

队列是按特定顺序保存对象的集合,同时应用 FIFO(先进先出)格式。它就像排队的人一样,只是数据不会插队。

function Queue(){
    var count = 0;
    //Yes, I don't use back and front.
    var head = null;
    var tail = null;

    //Returns the number of items in the queue
    this.GetCount = function(){
        return count;
    }

    /* Methods */
} 

我们的队列也将有 4 个附加方法:Enqueue(data)Dequeue()PeekAt()DisplayAll()

Enqueue

A stack image

描述:在队列前面添加一个项目。这个过程与堆栈的 PUSH 相同,但我为了练习而更改了它。

this.Enqueue = function (data) {
    //Creates a node with the data
    var node = {
        data: data,
        //next points to value straight way. If head is null, it won't be a problem
        next: head
    };

    //if it is the first item, then the head is also the tail
    if (head === null) {
        tail = node;
    }

    //defines the node as the new head
    head = node;

    //increases the count
    count++;
}

出队

A stack image

描述:移除并返回最后插入和存储的项目,该项目位于队列的另一侧。

this.Dequeue = function () {
    //if queue is empty, returns null
    if (count === 0) {
        return;
    }
    else {
        var current = head;
        var previous = null;

        //while there is a next, it will advance the queue.
        //the idea is to have "current" at the end and "previous" as the one before last
        while (current.next) {
            previous = current;
            current = current.next;
        }

        //if there is more than 1 item, 
        //Removes the tail and decreases count by 1.
        if (count > 1) {
            //Remove the reference to the last one.
            previous.next = null;

            //makes tail point to the previous node.
            tail = previous;
        }
        //resets the queue
        else {
            head = null;
            tail = null;
        }
        
        count--;
    }
}

显示所有

描述:名称说明了一切。它与 stack() 中的方法功能相同。

this.DisplayAll = function () {
    //
    //I will leave the analysis to you.
    //
    if (head === null) {
        return null;
    }
    else {
        var arr = new Array();
        var current = head;

        for (var i = 0; i < count; i++) {
            arr[i] = current.data;
            current = current.next;
        }

        return arr;
    }
}

查看

A stack image

描述:遵循 peek 的思想,但可以搜索和查看队列中的任何项目。

this.PeekAt = function (index) {
    //anything smaller than 0 and equal or greater than count is not at the queue
    if (index > -1 && index < count) {
        var current = head;

        //Navigates through the queue to find the item
        for(var i = 0; i < index; i++){
            current = current.next;
        }

        return current.data;
    }
    //an index out of the bounds of the queue was chosen.
    else {
        return null;
    }
}
  • 入队(数据):
  • 出队():
  • DISPLAYALL():
  • PEEKAT(索引):

3) 链表

A list image

链表是由节点组构成的数据结构,这些节点组共同表示一个序列。您会注意到队列和堆栈都是使用链表的基本思想构建的。但是,它们具有特殊的规则,使其在功能上有所不同。

function LinkedList() {
    var count = 0;
    var head = null;

    this.GetCount = function(){
        return count;
    }

    // Methods go here
} 

我们的链表将有 6 个附加方法:DisplayAll()DisplayAt(index)AddFirst(data)Add(data, index)RemoveFirst()RemoveAt

显示所有

描述:名称说明了一切。返回一个包含数据的数组,如果为空则返回 null。

this.DisplayAll = function () {
    //if is empty
    if (head === null) {
        return null;
    }
    else {
        //if not, runs trough the list and place it in an array.
        var arr = new Array();
        var current = head;

        for (var i = 0; i < count; i++) {
            arr[i] = current.data;
            current = current.next;
        }

        return arr;
    }
}

在...显示

描述:与队列中的上一个 PeekAt(index) 方法类似,在特定索引处显示,如果超出范围则返回 null。

this.DisplayAt = function (index) {
    //check for out-of-bounds values
    if (index > -1 && index < count) {
        var current = head;
        var i = 0;

        //this was not me, it is from nczonline(see source).
        //It is a different way to implelement from the FOR we've been using
        //and I wanted everyone have the chance to know it.
        while (i++ < index) {
            current = current.next;
        }

        return current.data;
    }
    else {
        return null;
    }
}

添加第一个

描述:添加到列表的开头。如果您想知道,开头是指索引为 0 并且由头节点引用的位置。

this.AddFirst = function(data) {
    //creates a new node
    var node = {
        data: data,
        next: head
    };

    head = node;

    count++;
}

Add

A list image

描述:在指定位置向列表中添加一个项目。

this.Add = function (data, index) {
    //if the chosen index is 0 do the AddFirst(data) method.
    if (index === 0) {
        this.AddFirst(data);
    }
    //check for out-of-bounds values
    else if (index > -1 && index < count) {

        var node = {
            data: data,
            next: null
        };

        var previous;
        var current = head;
        var i = 0;

        //find the right location
        while (i++ < index) {
            previous = current;
            current = current.next;
        }

        previous.next = node;
        node.next = current;

        count++;
    }
    else {
        alert("out of range");
    }
}

移除第一个

描述:移除第一个项目。

this.RemoveFirst = function () {
    //if no items on the list, returns null
    if (head === null) {
        return null;
    }
    else {
        var out = head;
        head = head.next;

        if (count > 0) {
            count--;
        }

        return out.data;
    }
}

移除于

A list image

描述:从特定索引移除一个项目

this.RemoveAt = function (index) {
    if (index === 0) {
        return this.RemoveFirst(index);
    }
    //check for out-of-bounds values
    else if (index > -1 && index < count) {

        var current = head;
        var previous;
        var i = 0;

        //find the right location
        while (i++ < index) {
            previous = current;
            current = current.next;
        }

        //skip over the item to remove
        previous.next = current.next;

        //decrement the length
        count--;
    }
    else {
        return null;
    }

    //return the value
    return current.data;
} 
  • DISPLAYALL():
  • 显示于(索引):
  • 添加第一个(数据):
  • 添加(数据,索引):
  • 移除第一个():
  • 移除于(索引):

4) 双端队列 (Double-ended queue)

A stack image

双端队列基本上像一个队列,只是你可以从两端添加或移除。既然您对它的工作原理更熟悉了,我想让事情变得更难一些。

function Deque() {
	var count = 0;
	var head = null;
	var tail = null;

	//Allows to view the value stored on Head
	this.getHead = function() {
		if (head) {
			return head.data;
		}

		return null;
	}
	
	//Allows to view the value stored on Tail
	this.getTail = function() {
		if (tail) {
			return tail.data;
		}
		return null;
	}
	
	//Returns the number of items
	this.GetCount = function() {
		return count;
	}
	
	//Lets define the node structure outside of each method.
	//This way it will need to be done only once
	var Node = function(data) {
		this.data = data;
		this.next = null;
	}
	
	//Methods go here
}

双端队列将比其他队列拥有更多的方法,总共有 10 个,包括您已经看到的方法和:DisplayHeadToTail()DisplayTailToHead()AddHead(data)AddTail(data)RemoveHead()RemoveTail()

从头到尾显示

描述:返回一个包含数据的数组,如果为空则返回 null。

this.DisplayHeadToTail = function() {
		if (head != null) {
			var arr = new Array();
			var current = head;
			
			//while there is a current
			while (current) {
				
				//ATTENTION: To those new to javascript, have a look. Can you guess what this method does?
				arr.push(current.data);
				current = current.next;
			}

			return arr;
		} else {
			return null;
		}
}

从尾到头显示

描述:从尾到头返回双端队列数据(与之前相反)。

this.DisplayTailToHead = function() {
		if (head != null) {
			
			//call DisplayHeadToTail() and reverse it.
			var arr = this.DisplayHeadToTail();
			
			//This is one of the many great methods from javascript.
			return arr.reverse();
		} else {
			return null;
		}
}

添加头

描述:添加到双端队列的前面(头)

this.AddHead = function(data) {
		
		//As you can see, now we only need to declare a new node
		var node = new Node(data);
		
		node.next = head;
		head = node;

		//if the list was empty
		if (!tail) {
			tail = head;
		}

		count++;
}

添加尾

描述:添加到双端队列的末尾(尾)

this.AddTail = function(data) {
		var node = new Node(data);
		
		//if the list was empty
		if (!head) {
			head = node;
		} else {
			tail.next = node;
		}

		tail = node;
		count++;
}

移除头

描述:从双端队列的前面(头)移除

this.RemoveHead = function() {
		if (head) {
			//If it's the last item
			if (count === 1) {
				head = null;
				tail = null;
			} else {
				head = head.next;
			}

			count--;
		}
}

移除尾

从双端队列的末尾(尾)移除

this.RemoveTail = function() {
		if (head) {
			//If it's the last item
			if (count === 1) {
				head = null;
				tail = null;
			} else {
				var current = head;
				
				//we need to go as far as the 2 before last
				while (current.next.next) {
					current = current.next;
				}

				tail = current;
				tail.next = null;
			}

			count--;
		}
}
  • DisplayHeadToTail():
  • DisplayTailToHead():
  • AddHead(data):
  • AddTail(data):
  • RemoveHead():
  • RemoveHead():

5) 双向链表

双向链表的工作原理与链表相同。但是,每个节点都包含对前一个节点和下一个节点的引用(如果存在)。当需要向后和向前遍历时,这特别有用。

function DoublyLinkedList(){
    var count = 0;
    var head = null;
    var tail = null;

	//Allows to view the value stored on Head
	this.getHead = function() {
		if (head) {
			return head.data;
		}

		return null;
	}
	
	//Allows to view the value stored on Tail
	this.getTail = function() {
		if (tail) {
			return tail.data;
		}
		return null;
	}
	
	//Returns the number of items
	this.GetCount = function() {
		return count;
	}
	
	//Defines the node
	var Node = function(data) {
		this.data = data;
		this.next = null;
		this.previous = null;
	}

    // Methods go here
} 

我们的双向链表将是最终的挑战,也是最长的一个,增加了 9 个方法:DisplayAll()DisplayAllBackwards()DisplayAt(index)AddFirst(data)AddLast(data)Add(data, index)RemoveFirst()RemoveFirst()RemoveAt

显示所有

描述:返回一个包含数据的数组,如果为空则返回 null。

this.DisplayAll = function () {
        //Most of the time, head won't be null, so lets start by that this time
    if (head) {
        var arr = new Array();
        var current = head;
        for (var i = 0; i < count; i++) {
            arr[i] = current.data;
            current = current.next;
        }
        return arr;
    }
    else {
        return null;
    }
}

倒序显示所有

描述:返回一个包含从尾到头数据的数组,如果为空则返回 null。

仔细看看这个方法,想想在普通链表中实现它会有多难。

this.DisplayAllBackwards = function(){
    if (head) {
        var arr = new Array();
        var current = tail;
        for (var i = 0; i < count; i++) {
            arr[i] = current.data;
            current = current.previous;
        }
        return arr;
    }
    else {
        return null;
    }
}

在...显示

描述:与链表的工作方式相同。

this.DisplayAt = function (index) {
    //check for out-of-bounds values
    if (index > -1 && index < count) {
        var current = head;
        var i = 0;

        //this was not me, it is from nczonline(see source).
        //It is a different way to implelement from the FOR we've been using
        //and I wanted everyone have the chance to know it.
        while (i++ < index) {
            current = current.next;
        }

        return current.data;
    }
    else {
        return null;
    }
}

添加第一个

描述:添加到双向链表的开头。

this.AddFirst = function (data) {
    //creates a new node
    var node = new Node(data);
    node.next = head;

    head = node;

    //if the list was empty
    if (count === 0) {
        tail = head;
    }
    else {
        //Don't forget about the previous node. It also needs to be referenced.
        head.next.previous = head;
    }

    count++;
}

添加最后一个

描述:添加到双向链表的末尾。

this.AddLast = function (data) {
    var node = new Node(data);
    node.previous = tail;

    if (count === 0) {
        head = node;
    }
    else {
        tail.next = node;
    }

    tail = node;

    count++;
}

Add

描述:在指定位置添加一个项目。提示:如有必要,画出过程图,它不像你想象的那么简单。

this.Add = function (data, index) {
    //check for out-of-bounds values
    if (index > 0 && index < count) {

        var node = new Node(data);
        var current = head;
        var i = 0;

        //find the right location
        while (i++ < index) {
            current = current.next;
        }

        current.previous.next = node;
        node.next = current;
        node.previous = current.previous;
        current.previous = node;

        count++;
    }
    else if (index < 1) {
        this.AddFirst(data);
    }
    else {
        this.AddLast(data);
    }
}

移除第一个

描述:移除第一个项目。

this.RemoveFirst = function () {
    if (head) {

        head = head.next;
        count--;
        //there was only one item
        if (count === 0) {
            tail = null;

        }
        else {
            //Don't forget about the previous node. It also needs the reference set to null.
            head.previous = null;

        }
    }
}

移除最后一个

描述:移除最后一个项目。

this.RemoveLast = function () {
    if (head) {
        //there is only one item
        if (count === 1) {
            head = null;
            tail = null;
        }
        else {
            tail.previous.next = null;
            tail = tail.previous;
        }

        count--;
    }
}

移除于

描述:从特定索引移除一个项目

this.RemoveAt = function (index) {
    //check for out-of-bounds values
    if (index > 0 && index < count-1) {

        var current = head;
        var i = 0;

        //find the right location
        while (i++ < index) {
            current = current.next;
        }

        current.previous.next = current.next;
        current.next.previous = current.previous;

        count--;
    }
    else if (index < 1) {
        this.RemoveFirst();
    }
    else {
        this.RemoveLast();
    }
}
  • DISPLAYALL():
  • 显示所有倒序():
  • 显示于(索引):
  • 添加第一个(数据):
  • 添加最后一个(数据):
  • 添加(数据,索引):
  • 移除第一个():
  • 移除最后一个():
  • 移除于(索引):

注释

这是一次很棒的经历,我记得我创建这些结构以及从中学习到的东西。

再次强调,可以更好地完成这些结构,我鼓励所有读者尝试一下。如果您不知道从何开始,这里有一些提示。

  • 尝试制定一个标准。我故意更改了一些方法,只是为了表明可以以不同的方式完成。
  • 改变一些东西。为什么只有移除返回价值?你能让所有东西都根据执行的成功返回 true 或 false 吗?
  • 你能用完全不同的方式做到吗?我挑战你使用不同的算法发布这 3 种数据结构。列表的数组不算 :P
  • 尝试将堆栈和队列实现为具有前一个和下一个引用的节点。
  • 尝试从链表的尾部到头部列出所有项目。

正如大家可能已经注意到的,英语不是我的第一语言,因此欢迎任何和所有的更正。代码的更正也一样。

如果我的工作允许,我希望能继续研究更难的数据结构。

来源

© . All rights reserved.