数据结构—01:单链表

目录

目录

一、单链表

例0:单链表反转

例1:判断链表 是否有环

例2:从尾到头打印出 链表每个结点的值

例3:链表求和,实现两数相加

例4:归并两个有序的链表

例5:从有序链表中删除重复结点

例6:删除链表的倒数第 n 个结点

例7:每k个长度的链表翻转,最后不够k个的不翻转



一、单链表

1.定义一个单链表:

public class LinkList<E> {
    //单链表长度
    private int size = 0;
    //链表的第一个结点
    private Node<E> firstNode;
    
    public LinkList() {}
	
    //头插
    public void addFirst(E e) {}
    //尾插
    public void addLast(E e) {}
    //按索引位置插入
    public void add(int index, E e) {}
    //判断指定数据是否存在链表中
    public boolean isExist(E e) {
    	return true;
    }
    //移除指定元素
    public boolean remove(Object o) {
    	return true;
    }
    //删除指定位置结点
    public void remove(int index) {}
    //删除第一个结点
    public E removeFirst() {}
    //删除最后一个结点
    public E removeLast() {}
    //获取指定位置元素
    public E get(int index) {}
    //获取单链表长度
    public int size() {
        return size;
    }
	
	//定义链表结点
	private static class Node<E> {
        private E data;
        private Node<E> nextNode;

        Node(E data, Node<E> nextNode) {
           this.data = data;
           this.nextNode = nextNode;
        }
    }
}

头插 尾插:都新增个节点newNode,

头插 newNode.next = firstNode;

尾插 lastNode.next = newNode;

2.头插法建立单链表

//头插
public void addFirst(E e) {
	 Node newNode = new Node(e,null);
	 if(firstNode == null) {
	     firstNode = newNode;
	 }else {
	     newNode.nextNode = firstNode;
	 }
	 size++;
}

3.尾插法建立单链表

//尾插
public void addLast(E e) {
	Node newNode = new Node(e,null);
	if(firstNode == null) {
	    firstNode = newNode;
	}
    // 遍历到最后一个节点
    lastNode.nextNode = newNode;
	size++;
}

4.特点:

  • 在单链表上插入、删除一个结点,必须知道其前驱结点
  • 单链表不具有按序号随机访问的特点,因此插入位置的查找只能从头指针开始一个一个顺序进行

5.链表分类:

  • 单链表
  • 循环单链表
  • 静态链表:用一维数组存储,next 域存放的是后继结点在数组中的下标
  • 双向链表:LinkedList

例0:单链表反转

方法一:递归法。核心思想:后一个的next 指向 前一个,前一个的next指向null

head.next.next = head;

head.next = null;

// 定义链表节点  
class ListNode {  
    int val;  
    ListNode next;  
  
    ListNode(int val) {  
        this.val = val;  
    }  
}  
  
public class LinkedListReversal {  
  
    // 反转链表的方法(递归)  
    public ListNode reverseList(ListNode head) {  
        // 递归终止条件:如果链表为空或只有一个节点,直接返回头节点  
        if (head == null || head.next == null) {  
            return head;  
        }  
          
        // 递归地反转head.next之后的链表,并返回反转后的头节点(原链表的第二个节点成为新的头节点)  
        ListNode newHead = reverseList(head.next);  
          
        // 将当前节点(原链表的头节点)的next指针指向前一个节点(即newHead),完成反转  
        head.next.next = head;  
          
        // 避免当前节点(原头节点)成为新链表的尾节点时形成环,将其next指针置为null  
        head.next = null;  
          
        // 返回新的头节点  
        return newHead;  
    }  
  
    // 辅助方法:打印链表  
    public void printList(ListNode head) {  
        ListNode current = head;  
        while (current != null) {  
            System.out.print(current.val + " -> ");  
            current = current.next;  
        }  
        System.out.println("null");  
    }  
  
    public static void main(String[] args) {  
        // 创建链表 1 -> 2 -> 3 -> 4 -> 5  
        ListNode head = new ListNode(1);  
        head.next = new ListNode(2);  
        head.next.next = new ListNode(3);  
        head.next.next.next = new ListNode(4);  
        head.next.next.next.next = new ListNode(5);  
  
        // 实例化LinkedListReversal对象  
        LinkedListReversal reverser = new LinkedListReversal();  
  
        // 反转链表  
        ListNode reversedHead = reverser.reverseList(head);  
  
        // 打印反转后的链表  
        reverser.printList(reversedHead);  
        // 输出应为:5 -> 4 -> 3 -> 2 -> 1 -> null  
    }  
}

方法二:三指针法

prev、curr、nextTemp 三个指针代表三个节点位置。curr 作为 循环主要的指针

    // 反转链表的方法(非递归)  
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {  
            return head;  
        }
  
        ListNode prev = head; 
        ListNode curr = head.next;

        while (curr != null) {  
            ListNode nextTemp = curr.next; // 保存 当前节点的下一个节点  
            curr.next = prev;   // 反转 当前节点的指向  
            prev = curr;        // 前一个节点 前进到当前节点  
            curr = nextTemp;    // 当前节点 前进到原来的下一个节点  
        }
 
        // 当循环结束时,curr为null,prev为新的头节点  
        return prev;  
    } 


例1:判断链表 是否有环

方法一:采用快慢指针,一个走的快些一个走的慢些, 如果最终相遇了就说明有环,时间复杂度 O(n),空间复杂度 O(1)

/*
 * 判断链表是否有环
 */
public boolean isLoop(Node first) {
	Node slow = first;
	Node fast = first.nextNode;
	
	while(fast != null) {
		//两引用指向同一结点,说明有环
		if(slow == fast)
			return true;
		
		//slow走慢点,fast走快点
		slow = slow.nextNode;
		fast = fast.nextNode.nextNode;
	}
	return false;
}

方法二:把每个节点放入hash表中,边找边放,先找后放,如果不为null,则已存在该结点,即有环

例1.1:如果链表有环,寻找环入口位置

public Node isLoop(Node first) {
	/***************************************/
	//从头取结点,放入一个链表
	LinkList link = new LinkList();
	Node temp = first;
	//如果该结点已在链表中存在,则该节点就是入口节点
	while(temp.nextNode != null) {
		if(link.isExist(temp)) {
			return temp;
		}else {
            link.addFirst(temp);
			temp = temp.nextNode;
		}
	}
	return null;
	/****************************************/
}


例2:从尾到头打印出 链表每个结点的值

方法一:使用递归,先递归打印后继结点,再打印当前结点。时间复杂度 O(lb n),空间复杂度 O(n)

public ArrayList<Integer> printListFromTailToHead(Node first) {
    ArrayList<Integer> list = new ArrayList<>();
    Node node = first;

    if (node!= null) {
        list .addAll(printListFromTailToHead(node.nextNode));
        list .add(node.data);
    }
    return list;
}

方法二:使用头插法,可以得到一个逆序的链表(翻转链表)。时间复杂度 O(n),空间复杂度 O(n)

public ArrayList<Integer> printListFromTailToHead(Node first) {
    // 头插法构建逆序链表
    Node head = new Node();
    Node node = first;
    while (node != null) {
        Node temp = node.next;
        node.next = head.next;
        head.next = node;
        node = temp;
    }
    // 构建 ArrayList
    ArrayList<Integer> list = new ArrayList<>();
    node = head.next;
    while (node != null) {
        list.add(node.data);
        node = node.next;
    }
    return list;
}

方法三:使用,栈具有后进先出的特点,在遍历链表时将值按顺序放入栈中,则出栈的顺序即为逆序

public ArrayList<Integer> printListFromTailToHead(Node first) {
    Stack<Integer> stack = new Stack<>();
    Node node = first;
    while (node != null) {
        stack.push(node.data);
        node = node.next;
    }
    ArrayList<Integer> list = new ArrayList<>();
    while (!stack.isEmpty())
    	list.add(stack.pop());
    return list;
}


例3:链表求和,实现两数相加

public Node addTwoNumbers(Node l1, Node l2) {
    Stack<Integer> stack1 = buildStack(l1);
    Stack<Integer> stack2 = buildStack(l2);
    Node head = new LinkList.Node();
    int carry = 0;
    
    while (!stack1.isEmpty() || !stack2.isEmpty() || carry != 0) {
        int x = stack1.isEmpty() ? 0 : stack1.pop();
        int y = stack2.isEmpty() ? 0 : stack2.pop();
        int sum = x + y + carry;
        Node node = new Node(sum % 10, null);
        node.nextNode = head.nextNode;
        head.nextNode = node;
        carry = sum / 10;
    }
    return head.nextNode;
}

private Stack<Integer> buildStack(Node node) {
    Stack<Integer> stack = new Stack<>();
    while (node != null) {
        stack.push(node.data);
        node = node.nextNode;
    }
    return stack;
}


例4:归并两个有序的链表

public Node mergeTwoLists(Node l1, Node l2) {
    if (l1 == null) return l2;
    if (l2 == null) return l1;
    if (l1.data < l2.data) {
        l1.nextNode = mergeTwoLists(l1.nextNode, l2);
        return l1;
    } else {
        l2.nextNode = mergeTwoLists(l1, l2.nextNode);
        return l2;
    }
}


例5:从有序链表中删除重复结点

public Node deleteDuplicates(Node head) {
    if (head == null || head.nextNode == null) return head;
    head.nextNode = deleteDuplicates(head.nextNode);
    return head.data == head.nextNode.data ? head.nextNode : head;
}


例6:删除链表的倒数第 n 个结点

public Node removeNthFromEnd(Node head, int n) {
    Node fast = head;
    while (n-- > 0) {
        fast = fast.nextNode;
    }
    if (fast == null) return head.nextNode;
    Node slow = head;
    while (fast.nextNode != null) {
        fast = fast.nextNode;
        slow = slow.nextNode;
    }
    slow.nextNode = slow.nextNode.nextNode;
    return head;
}


例7:每k个长度的链表翻转,最后不够k个的不翻转

import java.util.Scanner;
public class Main {
	static Node first;
	static Node temp;
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		String s = sc.next();
		int k = sc.nextInt();
		int len = s.length();
		for(int i = 0; i < len; i++) {
			addLast(s.charAt(i));
		}
		temp = first;
		for(int j = 0; j < len/k; j++) {
			for(int i = 0; i < k; i++) {
				if(i == 0) {
					reverse(temp);
				}
				temp = temp.next;
			}
		}
	}
	
	public static void addLast(char ch) {
		Node newNode = new Node(ch, null);
		if(first == null) {
			first = newNode;
			temp = first;
		}else {
			temp.next = newNode;
		}
		temp = newNode;
	}
	
	public static void reverse(Node node) {
		
	}
}

class Node{
	char data;
	Node next;
	
	Node(char data, Node next){
		this.data = data;
		this.next = next;
	}
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值