本篇博文实现的具体操作内容如下:
1. 遍历打印
2. 获取链表的长度
3. 通过下标获取链表中的某个数据
4. 通过下标设置链表中的某个数据
5. 在链表某一个节点之后加入一个新节点
6. 在链表末尾加入一个新节点
7. 删除一个链表节点
8. 链表倒序
0 链表的创建
//链表的数据结构
function Node(value,next){
this.value = value;
this.next = next;
}
//构造链表
/**
*
* @param {Array} arr 链表的每个结点的value值组成的数组
*/
function createNodes(arr){
let temp,root;
if(arr !== null){
root = new Node(arr[0],null);
temp = root;
}
if(arr.length > 1){
for(let i = 1; i < arr.length; i++){
const node = new Node(arr[i],null);
temp.next = node;
temp = node;
}
}
return root;
}
//const root = createNodes([1,2,3,4,5,6,7]);
1 遍历打印
//遍历打印(穷举法)
/**
*
* @param {Node} root 要打印的链表的头结点
*/
function print1(root){
let temp = root;
while(temp !== null){
console.log(temp.value);
temp = temp.next;
}
}
//遍历打印(递归)
//递归 = 规律 + 出口
/**
*
* @param {Node} root 要打印的链表的头结点
*/
function print2(root){
if(root!= null){
console.log(root.value);//只要我当前的结点不为空,我就先把自己打印出来
print2(root.next); //再让自己的下一个结点去判断自己是否为空,不是就打印出来....依次规律递归
}else{
return ; //出口,说明链表已经遍历完了,结束递归
}
}
2 获取链表的长度
//获取链表长度(穷取法)
/**
* @param {Node} root 链表头结点
* @return 返回链表的长度
*/
function getLength1(root){
let count = 0;
let temp = root;
while(temp !== null){
count++;
temp = temp.next;//指针后移
}
return count;
}
//获取链表长度(递归)
/**
* @param {Node} root 链表头结点
* @return 返回链表的长度
*/
function getLength2(root){
if(root === null){
return 0; //递归出口
}else{
return 1 + getLength2(root.next); //如果当前结点不为空,那么就计算当前结点和其之后的结点的个数之和
}
}
3 通过下标获取链表中的某个数据
//通过下标获取链表中的某个数据(穷区法)
/**
* @param {Node} root
* @param {Number} index
* @return 如果找得到,就返回该节点,否则返回0
*/
function findByIndex1(root,index){
let count = 0;
let temp = root;
while(temp !== null){
if(count === index){
return temp;
}
count++;
temp = temp.next;
}
return 0;
}
//通过下标获取链表中的某个数据(递归)
/**
* @param {Node} root
* @param {Number} index
* @return 如果找得到,就返回该节点,否则返回0
*/
function findByIndex2(root,index){
function _findByIndex2(node,cur){
if(cur === index){ //判断当前下标是否是需要寻找的下标
return node;
}
if(!node){
return 0; //已经超出链表范围
}
return _findByIndex2(node.next,cur+1);
}
return _findByIndex2(root,0);
}
4 通过下标设置链表中的某个数据
//通过下标设置链表中的某个数据
//思路:找到下标对应的结点,设置其数据
//穷取法
/**
*
* @param {Node} root
* @param {Number} index
* @param {*} value
* @return 返回1表示修改成功,返回0表示修改失败
*/
function setByIndex1(root,index,value){
let count = 0;
let temp = root;
while(temp !== null){
if(count === index){
temp.value = value;
return 1;
}
count++;
temp = temp.next;
}
return 0;
}
//通过递归
/**
* @param {Node} root
* @param {Number} index
* @param {*} value
* @return 返回1表示修改成功,返回0表示修改失败
*/
function setByIndex2(root,index,value){
function _setByIndex2(node,cur){
if(node === null){
return 0;
}
if(cur === index){
node.value = value;
return 1;
}
_setByIndex2(node.next,cur+1);
}
return _setByIndex2(root,0);
}
//其实可以直接调用上面的findByIndex1||findByIndex2方法
/**
* @param {Node} root
* @param {Number} index
* @param {*} value
* @return 返回1表示修改成功,返回0表示修改失败
*/
function setByIndex3(root,index,value){
const node = findByIndex1(root,index);//通过findByIndex1()或则findByIndex2()皆可
if(node !== 0){
node.value = value;
return 1;
}
return 0;
}
5 在链表某一个节点之后加入一个新节点
//在链表中的某一个节点插入一个新节点(穷取法)
/**
* @param {Node} root 头结点
* @param {Number} index 插入的下标位置,从0开始
* @param {*} value
* @return 返回1表示插入成功,返回0表示插入失败
*/
function addByIndex1(root,index,value){
//需要插入一个结点,就需要找到要插入结点的前一个结点,改变指针的指向
let temp = root;
let count = 0;
if(index <= 0 || index >= getLength1(root)){ //该算法不会改变头结点和尾结点
//这个输入的判断可加可不加,因为在循环里都是找不到的,但加上会好些,提高代码效率
return 0;
}
while(temp !== null){
if(count === index - 1){ //找到要插入的位置的前一个元素
const node = new Node(value,null);
node.next = temp.next;
temp.next = node;
return 1;
}
count++;
}
return 0;
}
//在链表中的某一个节点插入一个新节点(递归)
/**
* @param {Node} root 头结点
* @param {Number} index 插入的下标位置,从0开始
* @param {*} value
* @return 返回1表示插入成功,返回0表示插入失败
*/
function addByIndex2(root,index,value){
function _addByIndex2(node,cur){
if(node === null){
return 0;
}
if(cur === index - 1){ //找到了要插入的位置的前一个元素
const newNode = new Node(value,null);
newNode.next = node.next;
node.next = newNode;
return 1;
}
_addByIndex2(node.next,cur+1);
}
if(index <= 0 || index >= getLength1(root)){ //该算法不会改变头结点和尾结点
//这个输入的判断可加可不加,因为在循环里都是找不到的,但加上会好些,提高代码效率
return 0;
}else{
return _addByIndex2(root,0);
}
}
6 在链表末尾加入一个新节点
//在链表末尾加入一个新节点
/**
* @param {Node} root 头结点
* @param {*} value 新结点的value值
*/
//通过循环找到最后一个结点
function insertLast1(root,value){
let temp = root;
const node = new Node(value,null);
while(temp.next !== null){ //让指针指向最后一个结点
temp = temp.next;
}
temp.next = node;
}
//在链表末尾加入一个新节点
/**
* @param {Node} root 头结点
* @param {*} value 新结点的value值
*/
//通过递归找到最后一个结点
function insertLast2(root,value){
const newNode = new Node(value,null);
function _insertLast2(node){
if(node.next === null){ //找到了最后一个结点(递归的出口)
node.next = newNode;
return ;
}
_insertLast2(node.next);
}
_insertLast2(root);
}
7 删除一个链表节点
//通过下标删除一个结点
//重点:需要拿到要删除结点的前面一个结点
function deleteByIndex1(root,index){
let temp = root;
let count = 0;
if(index <= 0){ //不可以删除头结点
return 0;
}
while(temp !== null){
if(count === index - 1){ //找到了要删除的结点的前一个结点
temp.next = temp.next.next;
return 1;
}
}
return 0;
}
//通过递归
function deleteByIndex2(root,index){
if(index <= 0){
return 0;
}
function _deleteByIndex2(node,cur){
if(node === null){ //链表找完了(递归出口)
return 0;
}
if(cur === index-1){ //找到了要删除的结点的前一个结点
node.next = node.next.next;
return 1;
}
_deleteByIndex2(node.next,cur+1);
}
return _deleteByIndex2(root,0);
}
8 链表倒序
//链表倒序,利用递归
function reverseNodes(root){
let newRoot;
if(!root || !root.next){ //传入的头结点为空或者该链表只有一个结点时,就无需倒序
return root;
}
if(!root.next.next){ //只有两个结点的情况
newRoot = root.next; //先将第二个结点设置为新的头结点
root.next.next = root; //让第二个结点指向第一个节点
root.next = null; //再让第一个节点指向null
return newRoot;
}
else{ //大于两个结点
newRoot = reverseNodes(root.next); //让自己的下一个结点自己去倒序
root.next.next = root; //再处理自己
root.next = null;
return newRoot;
}
}
总结
以上就是对单向链表的一些常见操作的实现,基本上都通过了穷取法和分治法两种方式来实现。事实上,在设计有关链表的算法时,通过画数据结构图的方式可以更加直观的来反映出需要进行的操作,能够使在编程时思路更加清晰。以上代码如有考虑不当的地方,欢迎指出。