算法知识梳理(9) - 链表算法第一部分

一、概要

本文介绍了有关链表的算法的Java代码实现,所有代码均可通过 在线编译器 直接运行,算法目录:

  • 新建链表
  • 反转链表(递归和非递归实现)
  • 获得链表倒数第k个结点
  • 获得链表的中间结点
  • 删除链表结点
  • 交换链表结点

在本章的讨论当中,所有的链表都包含一个头结点Node,头结点不存储数据,其next指针指向第一个普通链表结点,每个普通链表结点包含一个int类型的数据项。

二、代码实现

2.1 新建链表

问题描述

输入一个int类型的数组,通过该数组创建一个链表,并打印出该链表的所有元素。

解决思路

首先我们创建一个首结点header,之后通过遍历数组p的方式获得数组中元素并创建对应的结点node,并进行两步操作:

  • 将首结点的当前后继结点,作为新结点的新后继结点
  • 将新结点作为首结点的新后继结点

因此,最终构建出来的链表中结点顺序是和原数组相反的。

代码实现

class Untitled {
	
	static class Node {
		public Node next;
		public int value;
	}
	
	static Node createList(int p[], int len) {
		Node header = new Node();
		Node curNode = null;
		for (int i=0; i<len; i++) {
			curNode = new Node();
			curNode.value = p[i];
			//将旧的第一个普通链表结点作为新结点的next。
			curNode.next = header.next;
			//将新结点作为第一个普通链表结点。
			header.next = curNode;
		}
		return header;
	}
	
	static void printList(Node header) {
		if (header != null) {
			Node node = header.next;
			while (node != null) {
				System.out.println("value=" + node.value);
				node = node.next;
			}
		}
	}
	
	public static void main(String[] args) {
		int p[] = {1,2,3,4,5};
		Node header = createList(p, p.length);
		printList(header);
	}
}
复制代码

运行结果

>> value=5
>> value=4
>> value=3
>> value=2
>> value=1
复制代码

2.2 反转链表

问题描述

将输入的链表进行反转,例如在2.1中创建的链表为header->5->4->3->2->1,那么反转后的链表为header->1->2->3->4->5

解决思路

这里我们介绍两种方式:非递归实现和递归实现。

实现代码

class Untitled {
	
	static class Node {
		public Node next;
		public int value;
	}
	
	static Node createList(int p[], int len) {
		Node header = new Node();
		Node curNode = null;
		for (int i=0; i<len; i++) {
			curNode = new Node();
			curNode.value = p[i];
			//将旧的第一个普通链表结点作为新结点的next。
			curNode.next = header.next;
			//将新结点作为第一个普通链表结点。
			header.next = curNode;
		}
		return header;
	}
	
	//(1)非递归实现
	static void reverseList(Node header) {
		if (header == null) {
			return;
		}
		//curNode表示待反转的结点。
		Node curNode = header.next;
		//nextNode表示待反转结点的下一个结点。
		Node nextNode = null;
		//curHeader表示已经完成反转的链表部分的第一个普通结点。
		Node curHeader = null;
		while (curNode != null) {
			nextNode = curNode.next;
			curNode.next = curHeader;
			curHeader = curNode;
			curNode = nextNode;
		}
		header.next = curHeader;
	}
	
	//(2)递归实现
	static void reverseListDi(Node header) {
		if (header == null) {
			return;
		}
		reverseListDiCore(header.next, header);
	}
	
	static Node reverseListDiCore(Node header, Node listHeader) {
		if (header.next == null) {
			listHeader.next = header;
			return header;
		}
		//下一个结点。
		Node nextNode = header.next;
		//对下一个结点进行反转。
		Node reverseNode = reverseListDiCore(nextNode, listHeader);
		//重新确立当前结点和下一个结点的关系。
		reverseNode.next = header;
		header.next = null;
		return header;
	}
		
	static void printList(Node header) {
		if (header != null) {
			Node node = header.next;
			while (node != null) {
				System.out.println("value=" + node.value);
				node = node.next;
			}
		}
	}
	
	public static void main(String[] args) {
		int p[] = {1,2,3,4,5};
		Node header = createList(p, p.length);
		reverseListDi(header);
		printList(header);
	}
}
复制代码

运行结果

>> value=1
>> value=2
>> value=3
>> value=4
>> value=5
复制代码

2.3 获得链表的倒数第 k 个结点

问题描述

输入一个链表,返回该链表的导入第k个结点(不包括首结点,最后一个结点为倒数第1个结点),如果链表的长度小于k,那么返回null

解决思路

采用 快慢指针 的思想,让fast先走k步,然后slow指针开始和fast指针一起走,当fast位于最后一个结点时,那么slow所在的位置就是倒数第k个结点。

代码实现

class Untitled {
    
    static class Node {
        public Node next;
        public int value;
    }
    
    static Node createList(int p[], int len) {
        Node header = new Node();
        Node curNode = null;
        for (int i=0; i<len; i++) {
            curNode = new Node();
            curNode.value = p[i];
            //将旧的第一个普通链表结点作为新结点的next。
            curNode.next = header.next;
            //将新结点作为第一个普通链表结点。
            header.next = curNode;
        }
        return header;
    }
    
	static Node getLastKNode(Node header, int k) {
		if (k < 1) {
			return null;
		}
		Node fast = header;
		Node slow = header;
		int step = 0;
		while (fast != null && fast.next != null) {
			fast = fast.next;
			step++;
			if (step >= k) {
				slow = slow.next;
			}
		}
		return slow != header ? slow : null;
	}
        
    static void printList(Node header) {
        if (header != null) {
            Node node = header.next;
			StringBuilder builder = new StringBuilder();
            while (node != null) {
				builder.append(String.valueOf(node.value));
                node = node.next;
				if (node != null) {
					builder.append("->");
				}
            }
			System.out.println(builder.toString());
        }
    }
    
    public static void main(String[] args) {
        int p[] = {1,2,3,4,5};
        Node header = createList(p, p.length);
		printList(header);
        Node kNode = getLastKNode(header, 4);
		System.out.println("KNode=" + (kNode != null ? kNode.value : ""));
    }
}
复制代码

运行结果

>> 5->4->3->2->1
>> KNode=4
复制代码

2.4 获得链表的中间结点

问题描述

输入一个链表,获得链表的中间结点:

  • 如果链表的长度为1,那么返回唯一的一个结点
  • 如果链表的长度为偶数,那么返回结点为其第len/2个结点,其中len为链表的长度
  • 如果链表的长度为奇数,那么len/2的值为x.5,取第x.5+0.5个结点作为返回结点

解决思路

2.3类似,采用 快慢指针 的方式,fast每次走两步,而slow每次走一步,当fast遍历到尾结点时,slow所处的位置就是中间结点。

实现代码

class Untitled {
    
    static class Node {
        public Node next;
        public int value;
    }
    
    static Node createList(int p[], int len) {
        Node header = new Node();
        Node curNode = null;
        for (int i=0; i<len; i++) {
            curNode = new Node();
            curNode.value = p[i];
            //将旧的第一个普通链表结点作为新结点的next。
            curNode.next = header.next;
            //将新结点作为第一个普通链表结点。
            header.next = curNode;
        }
        return header;
    }
    
	static Node geMiddleNode(Node header) {
		if (header == null || header.next == null) {
			return null;
		}
		Node fast = header;
		Node slow = header;
		while (fast != null) {
			fast = fast.next;
			if (fast != null) {
				fast = fast.next;
			} else {
				break;
			}
			slow = slow.next;
		}
		return slow;
	}
        
    static void printList(Node header) {
        if (header != null) {
            Node node = header.next;
			StringBuilder builder = new StringBuilder();
            while (node != null) {
				builder.append(String.valueOf(node.value));
                node = node.next;
				if (node != null) {
					builder.append("->");
				}
            }
			System.out.println(builder.toString());
        }
    }
    
    public static void main(String[] args) {
        int p[] = {1,2,3};
        Node header = createList(p, p.length);
		printList(header);
        Node kNode = geMiddleNode(header);
		System.out.println("KNode=" + (kNode != null ? kNode.value : ""));
    }
}
复制代码

2.5 删除链表结点

问题描述

输入一个链表的头结点header,并给出位于该链表中的一个结点dNode,要求从链表中删除该结点。

解决思路

这个问题最容易想到的做法就是找到待删除结点的前驱结点和后继结点,让前驱结点的next指向后继结点来实现删除,但是对于 待删除结点不是尾结点 的情况,我们可以采用一个小技巧:取出待删除结点的后继结点,再删除该后继结点,这样就避免了寻找前驱结点的过程。

实现代码

class Untitled {

	static class Node {
		public Node next;
		public int value;
	}

	static Node createList(int p[], int len) {
		Node header = new Node();
		Node curNode = null;
		for (int i=0; i<len; i++) {
			curNode = new Node();
			curNode.value = p[i];
			//将旧的第一个普通链表结点作为新结点的next。
			curNode.next = header.next;
			//将新结点作为第一个普通链表结点。
			header.next = curNode;
		}
		return header;
	}

	static Node getLastKNode(Node header, int k) {
		if (k < 1) {
			return null;
		}
		Node fast = header;
		Node slow = header;
		int step = 0;
		while (fast != null && fast.next != null) {
			fast = fast.next;
			step++;
			if (step >= k) {
				slow = slow.next;
			}
		}
		return slow != header ? slow : null;
	}

	static void deleteNode(Node header, Node dNode) {
		if (header == null && dNode != null) {
			return;
		}
		if (dNode.next != null) { 
			//如果不是尾结点,那么取其后继结点的值替换待删除结点。
			Node rNode = dNode.next;
			dNode.value = rNode.value;
			dNode.next = rNode.next;
		} else {
			//如果是尾结点,那么只能采用遍历的方式。
			Node node = header;
			while (node.next != null && node.next.next != null) {
				node = node.next;
			}
			node.next = null;
		}
	}

	static void printList(Node header) {
		if (header != null) {
			Node node = header.next;
			StringBuilder builder = new StringBuilder();
			while (node != null) {
				builder.append(String.valueOf(node.value));
				node = node.next;
				if (node != null) {
					builder.append("->");
				}
			}
			System.out.println(builder.toString());
		}
	}

	public static void main(String[] args) {
		int p[] = {1,2,3};
		Node header = createList(p, p.length);
		printList(header);
		Node kNode = getLastKNode(header, 3);
		System.out.println("KNode=" + (kNode != null ? kNode.value : ""));
		deleteNode(header, kNode);
		printList(header);
	}
}
复制代码

运行结果

>> 3->2->1
>> KNode=2
>> 3->1
复制代码

2.6 交换链表结点

问题描述

给定一个单链表的头结点header,和两个待交换的链表结点nodeAnodeB,交换这两个链表结点

解决思路

交互链表结点的关键,在于找到这两个结点的前驱和后继结点,修改它们和对应结点的引用关系,这里需要注意的是 交换结点相邻 的情况。

代码实现

class Untitled {
    
    static class Node {
        public Node next;
        public int value;
    }
    
    static Node createList(int p[], int len) {
        Node header = new Node();
        Node curNode = null;
        for (int i=0; i<len; i++) {
            curNode = new Node();
            curNode.value = p[i];
            //将旧的第一个普通链表结点作为新结点的next。
            curNode.next = header.next;
            //将新结点作为第一个普通链表结点。
            header.next = curNode;
        }
        return header;
    }
    
    static Node getLastKNode(Node header, int k) {
        if (k < 1) {
            return null;
        }
        Node fast = header;
        Node slow = header;
        int step = 0;
        while (fast != null && fast.next != null) {
            fast = fast.next;
            step++;
            if (step >= k) {
                slow = slow.next;
            }
        }
        return slow != header ? slow : null;
    }
	
	static void swapNode(Node header, Node nodeA, Node nodeB) {
		if (header == null || nodeA == null || nodeB == null) {
			return;
		}
		if (nodeA == header || nodeB == header) {
			return;
		}
		if (nodeA == nodeB) {
			return;
		}
		//找到nodeA的前驱结点
		Node preA = header;
		while (preA.next != nodeA) {
			preA = preA.next;
		}
		//找到nodeB的前驱结点
		Node preB = header;
		while (preB.next != nodeB) {
			preB = preB.next;
		}
		//nodeA和nodeB的后继结点
		Node postA = nodeA.next;
		Node postB = nodeB.next;
		//nodeA是nodeB的后继结点
		if (preB == nodeA) {
			nodeA.next = postB;
			nodeB.next = nodeA;
			preA.next = nodeB;
		//nodeB是nodeA的后继结点	
		} else if (preA == nodeB) {
			nodeB.next = postA;
			nodeA.next = nodeB;
			preB.next = nodeA;
		//nodeA和nodeB不相邻
		} else {
			preA.next = nodeB;
			nodeB.next = postA;
			preB.next = nodeA;
			nodeA.next = postB;
		}
	
	}
        
    static void printList(Node header) {
        if (header != null) {
            Node node = header.next;
            StringBuilder builder = new StringBuilder();
            while (node != null) {
                builder.append(String.valueOf(node.value));
                node = node.next;
                if (node != null) {
                    builder.append("->");
                }
            }
            System.out.println(builder.toString());
        }
    }
    
    public static void main(String[] args) {
        int p[] = {1,2,3,4,5};
        Node header = createList(p, p.length);
        printList(header);
        Node nodeA = getLastKNode(header, 5);
		Node nodeB = getLastKNode(header, 1);
		swapNode(header, nodeA, nodeB);
		printList(header);
    }
}
复制代码

运行结果

>> 5->4->3->2->1
>> 1->4->3->2->5
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
/* * 基于链表实现树结构 */ package dsa; public class TreeLinkedList implements Tree { private Object element;//树根节点 private TreeLinkedList parent, firstChild, nextSibling;//父亲、长子及最大的弟弟 //(单节点树)构造方法 public TreeLinkedList() { this(null, null, null, null); } //构造方法 public TreeLinkedList(Object e, TreeLinkedList p, TreeLinkedList c, TreeLinkedList s) { element = e; parent = p; firstChild = c; nextSibling = s; } /*---------- Tree接口中各方法的实现 ----------*/ //返回当前节点中存放的对象 public Object getElem() { return element; } //将对象obj存入当前节点,并返回此前的内容 public Object setElem(Object obj) { Object bak = element; element = obj; return bak; } //返回当前节点的父节点;对于根节点,返回null public TreeLinkedList getParent() { return parent; } //返回当前节点的长子;若没有孩子,则返回null public TreeLinkedList getFirstChild() { return firstChild; } //返回当前节点的最大弟弟;若没有弟弟,则返回null public TreeLinkedList getNextSibling() { return nextSibling; } //返回当前节点后代元素的数目,即以当前节点为根的子树的规模 public int getSize() { int size = 1;//当前节点也是自己的后代 TreeLinkedList subtree = firstChild;//从长子开始 while (null != subtree) {//依次 size += subtree.getSize();//累加 subtree = subtree.getNextSibling();//所有孩子的后代数目 } return size;//即可得到当前节点的后代总数 } //返回当前节点的高度 public int getHeight() { int height = -1; TreeLinkedList subtree = firstChild;//从长子开始 while (null != subtree) {//依次 height = Math.max(height, subtree.getHeight());//在所有孩子中取最大高度 subtree = subtree.getNextSibling(); } return height+1;//即可得到当前节点的高度 } //返回当前节点的深度 public int getDepth() { int depth = 0; TreeLinkedList p = parent;//从父亲开始 while (null != p) {//依次 depth++; p = p.getParent();//访问各个真祖先 } return depth;//真祖先的数目,即为当前节点的深度 } }
数据结构与算法是计算机科学的基础和核心领域之一,第ba章介绍了数据结构的相关内容。 本章主要包括以下几个方面的内容: 1. 线性结构:线性结构是指数据元素之间存在一对一的关系,包括线性表、栈和队列。线性表是最基本的数据结构之一,它分为顺序表和链表两种形式。顺序表使用数组实现,插入和删除操作相对低效。链表使用指针实现,插入和删除操作较为灵活。 2. 树结构:树结构是一种层次结构,由节点和边组成。常见的树结构有二叉树、二叉搜索树和平衡二叉树等。二叉树中每个节点最多有两个子节点,二叉搜索树中左子节点的值小于根节点,右子节点的值大于根节点,查找效率较高。平衡二叉树是一种保持左右子树高度差不大于1的二叉搜索树。 3. 图结构:图结构是由节点和边组成的非线性结构。图分为有向图和无向图。图的表示方法有邻接矩阵和邻接表两种。深度优先搜索和广度优先搜索是图的常用遍历方法,可用于寻找路径、连通分量等问题。 4. 排序算法:排序算法是对一组无序数据进行按照某个规则进行有序排列的算法。第ba章介绍了常见的排序算法,包括冒泡排序、插入排序、选择排序、归并排序、快速排序等。每种排序算法的时间复杂度和空间复杂度不同,选择合适的排序算法可以提高性能。 5. 查找算法:查找算法是在一组数据中搜索某个特定元素的算法。第ba章介绍了顺序查找、二分查找和哈希查找等常见的查找算法。二分查找是在有序数组中应用最广泛的查找算法,通过不断缩小查找范围来快速定位目标值。 通过学习这些内容,我们可以了解不同数据结构的特点和应用场景,以及常见的排序和查找算法。掌握好这些知识,对于解决实际问题和提高程序效率都有很大帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值