一、前端为什么要学算法和数据结构?
- 前言
其实,在前端,了解一些常用的Jvascript Array和Object并能够灵活运用就足以解决常见的初级和中级的算法问题,如果不够用还有很多的ECMAscript的API可以依赖。一个排序功能,用冒泡排序、快速排序还是并归排序?who care?一个原生的sort函数就直接搞定。况且前端的代码运行环境本来就不善于处理大量的数据计算。而且前端也有很多的东西比算法重要(比如安全性、用户体验、模块化和可扩展性等),在大多数情况下,前端并不在乎你写的程序有多快,更加在乎你能不能很好的实现功能。那我们为什么要学习算法与数据结构呢?
数据结构与算法是基础
前端工程师首先得是工程师,既然是计算机的工程师那么掌握一定的数据结构和算法并能够理解和灵活运用它们是一个最起码的要求。只有这样才能在日益复杂的前端领域站稳脚跟,越走越远。
有助于实现新的功能
当一个全新的功能摆在你面前又找不到现有的函数或api去实现的时候,良好的数据结构和算法功底就显的尤为重要。
锻炼能力
当你在学习数据结构和算法的时候,你的很多能力也会跟着提升。化繁为简,直达问题本质的能力、分析判断的能力、举一反三的能力 。
有利于面试和晋升
前端的面试离不开算法与数据结构,有用良好的该方面的知识储备量能够大大加分。
二、数据结构
数据结构:数据结构是数据元素相互之间存在的一种和多种特定的关系集合 包括二个部分组成逻辑结构,存储结构。
逻辑结构:简单的来说 逻辑结构就是数据之间的关系,逻辑结构大概统一的可以分成两种 一种是线性结构,非线性结构 。
线性结构:是一个有序数据元素的集合。 其中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的。常用的线性结构有: 列表,栈,队列,链表,线性表,集合。
非线性结构:各个数据元素不再保持在一个线性序列中,每个数据元素可能与零个或者多个其他数据元素发生联系。常见的线性结构有 二维数组,多维数组,广义表,树(二叉树等),图(网)等。
存储结构:逻辑结构指的是数据间的关系,而存储结构是逻辑结构用计算机语言的实现。 常见的存储结构有顺序存储、链式存储、索引存储以及散列存储(哈希表)。
大致结构如下:
三、线性结构--栈
- Javascript中的栈的实现
栈,是一种后入先出(LIFO)的线性结构。栈的头部,称为栈底;栈的尾部,称为栈顶。元素,从栈顶压入;从栈顶弹出。
如何实现一个栈:(1)确定存储结构:是离散的,还是连续的。或者说,是链表,还是连续表。在Javasript中可以选用数组,作为存储结构。
(2)确定操作方法:入栈方法(push),出栈方法(pop),预览栈顶方法(peek),清栈方法(clear),获取栈高方法(length)。具体实现如下:
function Stack() {
this.dataStore = [];
this.top = 0;
this.push = function ( element ) {
this.dataStore[this.top++] = element;
};
this.pop = function () {
return this.dataStore.splice(--this.top,1)[0];
};
this.peek = function () {
return this.dataStore[this.top-1];
};
this.length = function () {
return this.top;
};
this.clear = function () {
delete this.dataStore;
this.dataStore = [];
this.top = 0;
}
};
- 栈的应用
(1)进制转化:将十进制整数转化为任意正整数进制。
原理:取余法。
实现如下:
function newBase( num, base ) {
var stack = new Stack();
// 除n取余
do {
stack.push(num % base);
num = Math.floor(num/base);
} while (num > 0);
// 倒写余数
var str = '';
for (var i = 0, length = stack.length(); i < length; i++) {
str += stack.pop();
};
return str;
};
(2)阶乘的迭代实现
原理:将1到n依次压入栈中,再将栈顶弹出的数,一一相乘。
实现如下:
function newFactorial( n ) {
var stack = new Stack();
while (n > 1) {
stack.push(n--)
};
var result = 1;
while (stack.length() > 0) {
result *= stack.pop();
};
return result;
}
(3)判断单词是否回文
原理:倒写单词。将原单词从首字母到尾字母,依次压入栈中,再依次弹出,写入字符串,完成倒写,生成新单词。判断倒写之后的新单词和原单词是否相等。
实现如下:
function isPalindrome( words ) {
var stack = new Stack();
for (var i = 0; i < words.length; i++) {
stack.push(words[i]);
};
var rwords = '';
for (var i = 0, length = stack.length(); i < length; i++) {
rwords += s.pop();
};
if ( words === rwords ) {
return true;
} else {
return false;
};
};
(4)中缀表达式转化为后缀表达式(逆波兰表达式)
原理:自左向右扫描中缀表达式,如果当前是运算数,则压入栈中;如果当前是运算符(左括号,加减乘除),则压入栈;如果当前是运算符(右括号),则从栈中弹出,输出到后缀表达式中。直到栈顶不是(加减乘除)。再将左括号弹出。重复第一步和第二步,直到算术表达式扫描完毕。
实现如下:
function reversePolishExpression( arithmetic ) {
var operater = new Stack();
var reverseExp = '';
for (var i = 0; i < arithmetic.length; i++) {
if ( '0123456789'.indexOf(arithmetic[i]) !== -1 ) {
reverseExp += arithmetic[i];
} else if ( '(+-*/'.indexOf(arithmetic[i]) !== -1 ) {
operater.push(arithmetic[i]);
} else if ( arithmetic[i] === ')' ) {
while ( '+-*/'.indexOf(operater.peek()) !== -1 ) {
reverseExp += operater.pop();
};
operater.pop();
};
};
return reverseExp;
};
四、线性结构--队列
- Javascript中的队列的实现
队列是只允许在一端进行插入操作,另一个进行删除操作的线性表,队列是一种先进先出(FIFO)的数据结构
队列在程序程序设计中用的非常的频繁,因为javascript单线程,所以导致了任何一个时间段只能执行一个任务,而且还参杂了异步的机制。
队列本来也是一种特殊的线性表,在JavaScript我们可以直接使用数组实现这样的一个设计,数组的push()方法可以在数组末尾加入元素,shift()方法则可删除数组的第一个元素。
实现如下:
function Queue() {
this.dataStore = [];
this.enqueue = enqueue;
this.dequeue = dequeue;
this.first = first;
this.end = end;
this.toString = toString;
this.empty = empty;
this.enqueue = function(element) { //向队尾添加一个元素
this.dataStore.push(element);
}
this.dequeue = function() { //删除队首的元素
return this.dataStore.shift();
}
this.getFirst = function() { //读取队首元素
return this.dataStore[0];
}
this.getEnd = function() { //读取队尾元素
return this.dataStore[this.dataStore.length - 1];
}
this.showAll = function() { //显示队列中的所有元素
var retStr = "";
for (var i = 0; i < this.dataStore.length; ++i) {
retStr += this.dataStore[i] + "\n";
}
return retStr;
}
this.isEmpty = function() { //判断队列是否为空
if (this.dataStore.length == 0) {
return true;
} else {
return false;
}
}
}
- 队列的应用
(1)优先队列(可实现javascript桥接模式)
原理:数据成员入队的时候要携带数据和优先级,优先级在这里的定义是数字越大在数组中的位置靠后。当插入一个数组元素的时候,先要根据它的优先级来判断,最小是直接插入到第一位,紧邻一个比他大的优先级是,就插入到这个邻居的前面。如果是最大的数字就插到数组的最后。这样,一维数组变成了二位数组。
实现如下:
function PriorityQueue() {
let items = [];
function QueueElement (element, priority){
this.element = element;//成员本身
this.priority = priority; //优先级
}
this.enqueue = function(element, priority){
let queueElement = new QueueElement(element, priority);
let added = false; //添加标记
for (let i=0; i<items.length; i++){
if (queueElement.priority <
items[i].priority){ //找到优先级数字比他大的那个元素
items.splice(i,0,queueElement);
// 插到该元素的前面
added = true;
break;
}
}
if (!added){ //如果是优先级数字是最大的,直接插入到数组的末尾
items.push(queueElement); //{5}
}
};
//其他操作和普通队列是一样的
this.dequeue = function(){
return items.shift();
};
this.front = function(){
return items[0];
};
this.isEmpty = function(){
return items.length == 0;
};
this.size = function(){
return items.length;
};
this.print = function(){
for (let i=0; i<items.length; i++){
console.log(`${items[i].element} - ${items[i].priority}`);
}
};
}
let priorityQueue = new PriorityQueue();
priorityQueue.enqueue("zhao", 2);
priorityQueue.enqueue("wang", 1);
priorityQueue.enqueue("zhang", 1);
priorityQueue.enqueue("li", 2);
priorityQueue.enqueue("liu", 3);
priorityQueue.print();
//=>
//打印出的结果(按优先级排序)
wang - 1
zhang - 1
zhao - 2
li - 2
liu - 3
由浅入深,后续会有更多数据结构和算法的思考和整理。。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。