头节点不存任何数据,只用作链表的头,方便对链表增删改查等。
增加插入新节点
如果要将新节点插入到data1和data2之间,那么就找到插入位置,然后将data1的next指向newdata,newdata的next指向data2,这样就完成了添加插入。
如果想要把新节点插入到尾节点之后,直接将尾节点的next指向新节点即可。
删除节点
删除节点,比如这里要删除数据域为data2这个节点,将data2这个节点的前节点的next指向data2这个节点的next节点即可。由于data2这个节点没有任何引用,GC垃圾回收机制会处理这样的垃圾对象。
修改节点内容
通过遍历,找到需要更改的节点,对其内容更改即可。
查询节点
通过遍历,查询所有节点,查询指定节点。
实现很简单,就判断id值与下一个节点的id值的大小,如果添加节点的id小于判断的下一个节点的id,那么就在这个节点的前面插入新添加的节点。需要注意的是,需要判断链表中是否已存在该节点,这就避免了重复添加节点。
按顺序插入节点到指定位置。
代码
public class SingleLinkedListDemon {
public static void main(String[] args) {
SingleLinkedList singleLinkedList = new SingleLinkedList();
// singleLinkedList.addNode(new Node(1, "heroC","20"));
// singleLinkedList.addNode(new Node(3, "yikeX","21"));
// singleLinkedList.addNode(new Node(2, "wenxC","18"));
singleLinkedList.addNodeById(new Node(1, "heroC","20"));
singleLinkedList.addNodeById(new Node(3, "yikeX","21"));
singleLinkedList.addNodeById(new Node(2, "wenxC","18"));
singleLinkedList.addNodeById(new Node(2, "wenxC","18"));
singleLinkedList.showNode();
singleLinkedList.updateNode(new Node(4, "wenxC~~","20"));
// 修改id为2的节点
System.out.println("修改id为2的节点");
singleLinkedList.updateNode(new Node(2, "wenxC~~","20"));
singleLinkedList.delNode(3);
singleLinkedList.showNode();
}
}
class SingleLinkedList{
private final Node head = new Node(); // 创建一个头节点
// 向链表中添加节点,直接在尾节点后添加新节点
public void addNode(Node node){
Node temp = head; // 获取头节点
while (true){
// 判断当前节点的next是否指向节点对象,如果为null,说明当前节点是尾节点
if(temp.next == null){
// 向尾节点的next添加新节点
temp.next = node;
break;
}
// 如果不是尾节点,那么就next到指向到下一个节点对象
temp = temp.next;
}
}
// 优化添加方式,根据id大小添加元素到指定位置
public void addNodeById(Node node){
Node temp = head;
boolean flag = false;
while (true){
if(temp.next == null){
// 说明是temp是尾节点,退出此时的temp就是需要往后添加node的节点
break;
}
if(temp.id == node.id){
// id一样,说明链表中已添加了同id的节点对象
// flag为true,说明已存在同样id的节点
flag = true;
break;
}else if(temp.next.id > node.id){
// 如果temp下一个节点对象的id大于添加节点的id,那么这个添加的节点
// 就应该插入到temp节点的后面,temp节点的下一个节点的前面
// 退出此时的temp就是需要往后添加node的节点
break;
}
temp = temp.next; // 以上都不满足,就继续判断下一个节点
}
if(flag){
System.out.println("编号:"+ node.id + " 已在链表中存在,无法添加到链表中...");
}else {
// 插入节点操作
node.next = temp.next;
temp.next = node;
}
}
// 删除节点
public void delNode(int id){
Node temp = head;
boolean flag = false;
while (true){
if(temp.next == null){
flag = true;
break;
}
if(temp.next.id == id){
break;
}
temp = temp.next;
}
if(flag){
System.out.println("链表中无id为"+id+"的节点...");
}else {
System.out.println("已删除id为"+temp.next.id+"的节点");
temp.next = temp.next.next;
}
}
// 修改节点内容
public void updateNode(Node node){
Node temp = head.next;
while (true){
if(temp == null){
System.out.println("链表中无id为"+node.id+"的节点,无法修改...");
break;
}
if(temp.id == node.id){
temp.name = node.name;
temp.age = node.age;
break;
}
temp = temp.next;
}
}
// 遍历链表中的所有节点
public void showNode(){
Node temp = head.next; // 获取头节点的下一个节点
if(temp == null){ // 如果头节点的下一个节点为null,说明是空链表
System.out.println("链表为空...");
return;
}
while (true){
System.out.println(temp.toString());
temp = temp.next;
if(temp == null){
break;
}
}
}
}
// 创建一个节点对象
class Node{
// data域 ,在真实开发中应封装为一个类
public int id;
public String name;
public String age;
// next域
public Node next; // 用于存储下一个节点对象(指向下一个节点)
public Node() {
}
public Node(int id, String name, String age) {
this.id = id;
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Node{" +
"id=" + id +
", name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}
结果
编号:2 已在链表中存在,无法添加到链表中...
Node{id=1, name='heroC', age='20'}
Node{id=2, name='wenxC', age='18'}
Node{id=3, name='yikeX', age='21'}
链表中无id为4的节点,无法修改...
修改id为2的节点
已删除id为3的节点
Node{id=1, name='heroC', age='20'}
Node{id=2, name='wenxC~~', age='20'}
public class SingleLinkedListTest {
public static void main(String[] args) {
SinLinkedList sinLinkedList = new SinLinkedList();
sinLinkedList.addNode(new SinNode(3));
sinLinkedList.addNode(new SinNode(1));
sinLinkedList.addNode(new SinNode(4));
sinLinkedList.addNode(new SinNode(2));
sinLinkedList.showNode();
SingleLinkedListTest test = new SingleLinkedListTest();
// 测试第一题:
System.out.println("单链表中有效个数:"+test.getLinkedListLength(sinLinkedList.getHead()));
// 测试第二题:
System.out.println("倒数第2个节点:"+test.getSinNode(sinLinkedList.getHead(),2));
// 测试第三题:
test.reverseSinNode(sinLinkedList.getHead());
System.out.println("链表反转:");
sinLinkedList.showNode();
// 测试第四题:
System.out.println("倒叙打印链表:");
test.reversePrintNode(sinLinkedList.getHead());
// 测试第五题:
System.out.println("第一个有序链表:");
SinLinkedList sinLinkedList1 = new SinLinkedList();
sinLinkedList1.addNode(new SinNode(2));
sinLinkedList1.addNode(new SinNode(5));
sinLinkedList1.addNode(new SinNode(6));
sinLinkedList1.addNode(new SinNode(9));
sinLinkedList1.showNode();
System.out.println("第二个有序链表:");
SinLinkedList sinLinkedList2 = new SinLinkedList();
sinLinkedList2.addNode(new SinNode(1));
sinLinkedList2.addNode(new SinNode(3));
sinLinkedList2.addNode(new SinNode(7));
sinLinkedList2.addNode(new SinNode(12));
sinLinkedList2.showNode();
System.out.println("合并结果:");
SinNode sinNode = test.merge(sinLinkedList1.getHead(), sinLinkedList2.getHead());
if(sinNode!=null){
while (sinNode.next!=null){
System.out.println(sinNode.next);
sinNode = sinNode.next;
}
}
}
// 第一题:代码
// 第二题:代码
// 第三题:代码
// 第四题:代码
// 第五题:代码
}
class SinLinkedList{
private final SinNode head = new SinNode();
public SinNode getHead() {
return head;
}
public void addNode(SinNode node){
SinNode temp = head;
boolean flag = false;
while (true){
if(temp.next == null){
break;
}
if(temp.next.id > node.id){
break;
}else if(temp.next.id == node.id){
flag = true;
break;
}
temp = temp.next;
}
if(flag){
System.out.println("链表中已有该节点...");
}else {
node.next = temp.next;
temp.next = node;
}
}
public void showNode(){
SinNode temp = head.next;
while (true){
if(temp==null){
break;
}else {
System.out.println(temp);
}
temp = temp.next;
}
}
}
class SinNode{
public int id;
public SinNode next;
public SinNode() {
}
public SinNode(int id) {
this.id = id;
}
@Override
public String toString() {
return "SinNode{" +
"id=" + id +
'}';
}
}
思路:直接遍历单链表,并通过变量记录有效节点数
public int getLinkedListLength(SinNode head){
SinNode temp = head;
int length = 0;
while (true){
if(temp.next == null){
return length;
}
length++;
temp = temp.next;
}
}
(新浪)思路:因为单链表只能从头遍历到尾,要找到倒数第k个节点,就先得遍历一遍链表得到链表长度size
,因此要得到倒数第k
个节点就是找到size-k
位置的节点即可。从head节点的next节点开始计算,那么找到倒数第k个基点就是需要移动size-k
次。
// 获得倒数第k个节点
public SinNode getSinNode(SinNode head, int k){
SinNode temp = head.next;
int size = getLinkedListLength(head); // 获得有效节点数
if(temp == null){
return null;
}
if(k<=0 || k>size){
return null;
}
for (int i = 0; i < size-k; i++) {
temp = temp.next;
}
return temp;
}
(腾讯)思路:新定义一个链表用于存储反转后的链表,遍历原链表,将原链表的每一个节点都依次插入到新链表的头部的next节点上。最后将原链表指向新链表就完成了反转。
// 链表反转
public SinNode reverseSinNode(SinNode head){
// 当链表为空或者链表中只有一个节点的时候,直接返回
if(head.next == null || head.next.next == null){
return head;
}
SinNode reverseNode = new SinNode();
SinNode currentNode = head.next;
SinNode curNextNode;
// curNextNode用于记录当前节点的下一个节点,为了保证链表不断裂,能够持续遍历。
// 因当前节点加到新链表中,要想将原链表的当前节点的下一个节点继续添加到新链表的头部,就必须记录下来
while (currentNode != null){
// 先把当前节点的下一个节点记录下来
curNextNode = currentNode.next;
// 把当前节点的下一个节点连接到反转链表头部指向的下一个节点,
// 这样反转链表除了头部其余节点都在当前节点后面了
currentNode.next = reverseNode.next;
// 将反转链表的头部指向当前节点,形成新的反转链表,此时当前节点已成功从反转链表头部插入
reverseNode.next = currentNode;
// 最后将之前记录的原链表的当前节点的下一个节点赋值给当前节点,成为需要添加到反转链表的节点
currentNode = curNextNode;
}
head.next = reverseNode.next; // 将原链表指向反转后的链表
return head;
}
(百度)思路:遍历链表,以此将每个节点压入栈(先进后出)中,最后遍历栈即可。
// 从尾到头打印链表
public void reversePrintNode(SinNode head){
if(head.next == null){
return;
}
SinNode temp = head.next;
Stack<SinNode> stack = new Stack<>();
while (temp!=null){
stack.push(temp);
temp = temp.next;
}
while (!stack.isEmpty()){
System.out.println(stack.pop());
}
}
思路:定义一个新的链表,两个有序链表在加入到新链表中时,比较节点大小,小的先加入即可
// 合并两个有序链表并返回这个合并的链表
public SinNode merge(SinNode head1, SinNode head2){
if (head1.next == null){
return head2;
}else if (head2.next == null){
return head1;
}
SinNode merge = new SinNode();
SinNode tempMerge = merge; // 1
SinNode temp1 = head1.next;
SinNode temp2 = head2.next;
while (true){
if(temp1==null && temp2==null){
break;
}else if(temp1==null){
tempMerge.next = temp2;
break;
}else if (temp2 == null){
tempMerge.next = temp1;
break;
}
if(temp1.id < temp2.id){
tempMerge.next = temp1;
temp1 = temp1.next;
}else{
tempMerge.next = temp2;
temp2 = temp2.next;
}
tempMerge = tempMerge.next;
}
return merge;
}
写第5题时,突然有了疑惑。为什么必须要1编号位置的代码,辅助节点?
这个辅助变量是用于遍历或者对链表操作而用的,这个变量的指向一直在改变,如果不需要辅助节点,直接使用这里的merge变量去操作,也是可以将两个有序链表合并,但是返回的是merge,这时候返回的就是merge被定位的位置节点,就不能返回一个完整的链表。如果借用辅助节点,对merge对象进行操作,由于merge的角色一直没有变,一直是头节点,返回的也就是头节点,这样就能愉快的使用整个链表的数据。
为什么第三题,新链表reverseNode不需要辅助节点?
很简单,因为向reverseNode链表中添加的时候,一直是从头节点添加节点,reverseNode的角色也是一直没有变,一直是头节点,返回的也就是头节点。但也可以使用辅助节点。
总之,要确保返回的是头节点,这样才能愉快的使用或获取整个单向链表的数据。