最近在复习数据结构的过程中,发现基本上数据结构都是用C来实现的,自己之前学习的时候也是用C去写的,由于目前对js更为熟悉一些,所以这里选择使用js去实现其中的某些算法和结构。实际上算法和语言关系不大,很多数据结构教材的作者也鼓励读者使用自己熟悉的语言去重写其中的代码,而最近连leetcode也已经开始支持js了,再次证明了js这门语言的活跃度。本文首先使用js来实现线性表。
关于线性表的概念这里就不赘述了,属于老生常谈的话题,线性表按照存储(物理)结构分为顺序存储和链式存储,每种存储方式的不同决定了它的实现代码是不同的:
1.顺序存储结构
顺序存储的特点就是在内存中选一块连续的地址空间,然后将线性表放入其中,这样做便于线性表的存取,但是不利于插入和删除,而且在事先无法确定线性表长度的前提下可能会造成内存浪费或溢出。
这里为了查看以及插入删除的方便,使用HTML只做了一个界面,通过界面来操作线性表顺序存储结构的插入删除和获取。
HTML代码:
<p> <h3>插入元素</h3> <input type="text" id="name" placeholder="姓名"/> <label for="sex">性别</label> <select id="sex" > <option value="male">男</option> <option value="female">女</option> </select> <input type="text" placeholder="索引(从0开始)" id="index"/> <input type="button" value="插入" id="insert"> <input type="button" value="获取" id="get"> <input type="button" value="删除" id="del"> </p> <h3>结果</h3> <p id="result"></p>
顺序存储下的每个元素是一个对象,拥有姓名和性别两个数据项
初始化顺序存储的线性表并获取DOM元素:
var sqList=[],insert=document.getElementById('insert'),result=document.getElementById('result') ,getBtn=document.getElementById('get'),delBtn=document.getElementById('del');
这里的线性表实际上在js中就是一个数组。
顺序存储结构的插入元素:
function insertItem(name,sex,i){ var obj={}; obj.name=name; obj.sex=sex; if(i<0||i>sqList.length){ result.innerHTML+="<br>非法的索引位置"+i+"<br>"; } else{ for(var j=sqList.length-1;j>=i;j--){ sqList[j+1]=sqList[j]; } sqList[i]=obj; result.innerHTML+="<br>从索引"+i+"处插入一个元素<br>"+JSON.stringify(sqList); } }
该函数接受三个参数,前两个是线性表数据元素中的name和sex属性,最后一个参数表示将这个元素插入到第i个位置(这里i是从0开始的),每次插入成功后会打印出当前结果。
获取元素
function getItem(index){ if(index<0||index>sqList.length){ result.innerHTML+="<br>非法的索引位置"+i+"<br>"; } for(var k=0;k<sqList.length;k++){ if(k==index){ result.innerHTML+="<br>获取索引为"+index+"的元素:<br>"+JSON.stringify(sqList[k]); break; } } }
这个函数接受一个参数用来表示获取第index个元素(index从0开始),获取成功后打印出来该元素。
删除元素:
function deleteItem(index){ var delObj=sqList[index]; if(sqList.length==0){ result.innerHTML+="<br>线性表为空"; return; } if(index<0||index>sqList.length-1){ result.innerHTML+="<br>非法的索引位置"+i+"<br>"; return; } //如果删除的不是最后一个元素 else if(index<sqList.length-1){ for(var i=index;i<sqList.length-1;i++){ sqList[i]=sqList[i+1];
} } sqList.length--; result.innerHTML+="<br>删除的元素:<br>"+JSON.stringify(delObj)+"<br>被删除后的顺序表:<br>"+JSON.stringify(sqList); }
这个函数接受一个index参数,用来表示删除第index个元素(index从0开始),删除成功后打印出被删除元素和当前线性表。
事件监听进行调用代码
//插入调用 insert.onclick=function(){ var name=document.getElementById("name").value, selectIndex=document.getElementById("sex").selectedIndex, male=document.getElementById("sex").options[selectIndex].value, index=document.getElementById("index").value; insertItem(name,male,index); }; //获取调用 getBtn.onclick=function(){ var index=document.getElementById("index").value; getItem(index); }; //删除调用 delBtn.onclick=function(){ var index=parseInt(document.getElementById("index").value); deleteItem(index); };
结果如下:
2.链式存储结构
链式存储结构地址是离散的,是在内存中任意找个地方把某个元素存储,元素和元素之间采用链接关系,即当前元素的next指针指向的是该链表中的下一个元素,整个链表是靠指针链接起来的。这就避免了内存浪费或溢出,但是对于获取某个元素却比顺序结构要麻烦,因为要遍历整个链表。通常顺序表的链式存储结构最典型的实现就是单链表。
定义单链表节点:
function LinkNode(data,next){ this.data=data; this.next=next; }
该单链表中的节点拥有两个数据项,data为数据域,next为指针域
单链表初始化
function createLink(){ var list=new LinkNode(0,null); return list; }
单链表初始化操作实际上是定义了一个空链表,这个空链表拥有一个头结点,头结点的数据域data存储的不是真的数据,而是该链表长度,同时,当链表为空的时候,头结点的next指针域是null的。
单链表插入操作
function insertItem(list,i,data){ //找到第一个节点 var obj=list; //找到第一个位置 var j=1; while(obj&&j<i){ ++j; obj=obj.next; } //这里的j>i表示i是小于等于0的位置 if(!obj||j>i){ console.log("插入位置不正确"); return; } var sNode=new LinkNode(data,obj.next); obj.next=sNode; list.data+=1; }
该插入函数接受三个参数,list为被插入的链表,i为被插入的位置(这里为了方便,让i从1开始,就是说只有大于等于1的i才是合法的插入位置),data是被插入节点的数据域的数据,思想就是遍历该链表,找到被插入位置,然后进行插入,注意这里由一个寻找位置的过程,该过程时间复杂度是O(n),插入过程时间复杂度为O(1),因为不需要移动元素,只需要改变指针指向。插入成功之后需要将头结点的data域加1,因为该处存储的是单链表长度
单链表获取元素操作
function getItem(list,i){ var j=1; //先找到第一个节点 var obj=list.next; //当obj存在并且还没有到第i个节点的时候 //循环继续 while(obj&&j<i){ ++j; obj=obj.next; } if(!obj||j>i){ console.log("元素不存在"); return; } return obj; }
该函数有两个参数,list是要被查找的链表,i是要查找的位置(从1开始)
单链表删除操作
function deleteItem(list,i){ var j=1; //这里让obj等于list是为了找到被删除元素的前驱节点 var obj=list; while(obj.next&&j<i){ ++j; obj=obj.next; } if(!(obj.next)||j>i){ console.log("非法的删除位置"); return; } var item=obj.next; obj.next=item.next; list.data-=1; item=null; return list; }
该函数接受两个参数,list是被删除链表,i为被删除位置(i从1开始),也是分了查找删除位置和进行删除操作两个部分,删除成功之后,记得要将头结点data减1,同时为了让GC尽快回收被删除元素占用的空间,这里让其item=null(在C语言中可以 采用free函数,在js中设为null会让浏览器尽快通过引用计数或标记删除策略通知GC回收空间)。
随机生成获取删除代码如下
//随机生成一个拥有五个元素的链表 function createRandomList(){ var list=createLink(); for(var i=0;i<5;i++){ insertItem(list,1,Math.round(Math.random()*10)); } console.log("随机生成的结果是:"); console.log(JSON.stringify(list)); return list; } //随机获得某个元素 function getRandomItem(list){ //获取A到B之间的随机数为Math.round(Math.random()*(b-a))+a; var i=Math.round(Math.random()*4)+1; console.log("随机获得第"+i+"个元素的数据是:"); console.log((getItem(list,i).data)); } //随机删除某个元素 function deleteRandomItem(list){ var i=Math.round(Math.random()*4)+1; var result=deleteItem(list,i); console.log("随机删除第"+i+"个元素之后,结果为:"); console.log(JSON.stringify(result)); }
这里为了简单直接随机生成一个拥有5个元素的单链表,其数据采用随机生成方式(0-9)之间的随机数,然后可以随机获取某个元素,还可以随机删除某个元素
最终调用代码如下:
var list=createRandomList(); getRandomItem(list); deleteRandomItem(list);
这里随机生成了一个链表又随机获取其中某个元素,再随机删除了其中某个元素,为了便于查看,将结果采用JSON字符串的形式打印在控制台上,结果如下:
注意随机生成的链表的第一个节点的data为5不是数据,而是链表的整体长度为5.