跳到主要内容

06、数据结构与算法 - 实战:单向链表 - Java

概念

逻辑有序

 

内存上节点间无序

 

特点

1、 由节点组成,是逻辑上有序的、内存上无序
2、 每个节点由数据(data)、以及指向下一个节点的指针(next)组成
3、 next=null:说明链表已经遍历完节点
4、 head指针(头节点不存数据):指向第一个节点的位置

单链表 - 新元素直接添加到尾部

代码实现

通用的简单单向链表 - 泛型

class  SingleLinkedList<T> {
   
     
	
	// 头节点
	Node<T> head = new Node<T>(null);
	
    // 添加元素
	public void add(T a) {
   
     
		
		// 将添加的元素 封装成为 一个新的节点
		Node<T> newNode = new Node<T>(a);
		
		// 用来存储当前遍历到的节点引用
		Node<T> temp_Node = head;
		
		while(true) {
   
     
			
			// 如果节点中含有 next=null 说明则是尾节点,停止遍历
			if(temp_Node.next == null) {
   
     
				break;
			}
			temp_Node = temp_Node.next;
		}
		
		// 尾节点的next= 新节点的引用
		temp_Node.next = newNode;
	}
	
	
	// 将链表以字符串的形式输出、故重载 toString() 方法
	@Override
	public String toString() {
   
     
		
		// 存储当前遍历到的节点的引用
		Node<T> temp_Node = head;
		
		// 使用 StringBuilder处理字符串更加的快
		StringBuilder sb = new StringBuilder();
		
		// 只有到了 尾节点,节点遍历才停止
		while(temp_Node.next != null) {
   
     
			
			temp_Node = temp_Node.next;
			sb.append(temp_Node.data + "\n");
		}
		
		sb.delete(sb.lastIndexOf("\n"), sb.length());
		
		return sb.toString();
	}
	
	
	// 1. 内部节点类无需显示地定义在列表的外面,单纯私有内部类就可以了
	@SuppressWarnings({
   
      "unused", "hiding" })
	private class Node<T> {
   
     
		T data;    // 存储T类型的真实数据
		Node<T> next;   // 指向下一个节点
		Node(T data) {
   
     
			this.data = data;
			next = null;
		}
		
		@Override
		public String toString() {
   
     
			return data.toString();
		}
		
	}
	
}

测试

class Student {
   
     
	String name;
	int year;
	public Student(String name, int year) {
   
     
		this.name = name;
		this.year = year;
	}
	@Override
	public String toString() {
   
     
		return "Student [name=" + name + ", year=" + year + "]";
	}
	
	@Override  // 方便后面测试
	public int hashCode() {
   
     
		return year;
	}
    
    
    public boolean equals(Object o) {
   
     
		
		if(! (o instanceof Student) ) {
   
      return false; }
		
		if(name.equals( ((Student)o).name )  && year == ((Student)o).year) {
   
     
			return true;
		}
		return false;
		
	}

    
}
// 测试代码
public static void main(String[] args) {
   
     		
		Student stu1 = new Student("lrc", 22);
		Student stu2 = new Student("glw", 27);
		Student stu3 = new Student("yxe", 25);
		
		SingleLinkedList<Student> sll = new SingleLinkedList<Student>();
		
		sll.add(stu1);
		sll.add(stu2);
		sll.add(stu3);
		
		System.out.println(sll);
}

运行结果
 

单链表 - 新元素动态添加到链表

按hashCode()的大小动态插入,链表越后面的元素其hashCode()越大

代码实现

在上面的通用简单链表添加一个成员方法

public void addOrder(T a) {
   
     

    // 1. 需要添加的新元素节点
    Node<T> newNode = new Node<T>(a);

    // 2. 找到新元素插入该临时节点的后面
    Node<T> temp_Node = headNode;
    // 3. 遍历链表
    while(temp_Node.next != null) {
   
     
        // 如果找到临时节点的后一个节点比新节点大,则直接停止循环
        if(temp_Node.next.data.hashCode() > a.hashCode()) {
   
     
            break;
        }

        temp_Node = temp_Node.next;
    }

    // 4. 插入节点步骤
    newNode.next = temp_Node.next;
    temp_Node.next = newNode;

}

测试代码

public static void main(String[] args) {
   
     

    Student stu1 = new Student("lrc", 22);
    Student stu2 = new Student("glw", 27);
    Student stu3 = new Student("yxe", 25);

    SingleLinkedList<Student> sll = new SingleLinkedList<Student>();

    sll.addOrder(stu1);
    sll.addOrder(stu2);
    sll.addOrder(stu3);

    System.out.println(sll);

}

运行结果
 

单链表 - 链表元素删除

代码实现

public boolean delete(T a) {
   
     
    Node<T> temp = headNode;

    // 遍历完、或者 找到被删元素的前一个节点则停止遍历
    while(temp.next != null) {
   
     

        // 被删元素的前一个节点
        if(temp.next.data.equals(a)) {
   
     
            break;
        }
        temp = temp.next;
    }

    // 如果是链表最后一个节点、所以链表没有符合实参的节点
    if(temp.next == null) {
   
     
        return false;
    }else {
   
     

        // 该为被删的节点
        Node deletingNode = temp.next;

        // 被删节点后面没有元素
        if(deletingNode.next == null) {
   
     
            temp.next = null;
        // 被删节点后面有元素
        }else {
   
     
            temp.next = deletingNode.next;
        }
        return true;
    }

}

单链表 - 获取链表有效节点的个数

代码实现

// 统计有效节点的个数 --  不算头指针(即头节点)
public int length() {
   
     
    Node<T> temp = headNode;
    int length = 0;
    while(temp.next != null) {
   
     
        temp = temp.next;
        length++;
    }

    return length;
}

单链表 - 获取倒数第n个节点的数据

代码实现

public T lastElement(int n) {
   
     

    if(length() < n) {
   
     
        throw new RuntimeException("超出界限,链表无这元素");
    }else {
   
     

        // 用来保存查找到的元素
        Node<T> temp2 = null;

        // n有几次,就遍历几次
        for(int i = 0; i < n; i++) {
   
     

            // 每遍历一次就从头开始
            Node<T> temp1 = headNode;
            while(temp1.next != temp2) {
   
     
                temp1 = temp1.next;
            }

            // 将查找到元素放置在temp2
            temp2 =temp1;
        }

        return temp2.data;
    }

}	

单链表 - 节点反转

代码实现

public void reverse() {
   
     
    if(length() == 0) {
   
     
        return;
    }else {
   
     

        // 创建反转节点链的头指针
        Node<T> reverHead = new Node<T>(null);

        // 遍历从第一个有效节点开始,而不是头指针 ---  这部非常重要
        Node<T> validNode = headNode.next;  

        // 用来存储当前有效节点的下一个节点
        Node<T> next = null;  
        while(validNode != null) {
   
     
            // 1. 存储下一个节点的位置信息
            next = validNode.next;

            // 2. 当前节点的next指针指向  reverHead头指针指向的节点
            validNode.next = reverHead.next;

            // 3. reverHead指针指向当前节点
            reverHead.next = validNode;

            // 4. 指向下一个节点
            validNode = next;
        }

        headNode = reverHead;
    }

}

单链表 - 从尾开始打印节点

代码实现1 - 改变链表结构

// 从尾到头打印节点  ---  消耗太多性能,因为改变了两次链表结构
public void reversePrint1() {
   
     
    // 1. 先反转链表结构
    reverse();
    // 2.打印
    System.out.println(this);
    // 2.在反转回原来链表结构
    reverse();
}

代码实现2 – 利用堆栈存储节点

public void reversePrint2() {
   
     
  
  // 1. 存储节点
    Stack<Node<T>> stack = new Stack<Node<T>>();

    // 2. 上一个真实节点的指针
    Node<T> temp = headNode;

    while(true) {
   
     

        // 3. 待添加的节点
        Node<T> addingNode = temp.next; 

        // 4. 如果待添加的节点为空,则退出循环
        if(addingNode == null) {
   
     
            break;
        }

        // 5. 将待添加的节点 加入堆栈
        stack.push(addingNode);

        // 6. 移动遍历的指针
        temp = addingNode;
    }

    // 7. 利用堆栈先进后出的属性 从尾到头打印节点
    while(!stack.empty()) {
   
     
        System.out.println(stack.pop());
    }

}

单链表 - 合并两个有序的链表

代码实现

public void mergeOrder(SingleLinkedList<T> sll) {
   
     

    // 注意这里是从头指针开始  -- 进行遍历操作-- 记录当前遍历的位置
    Node<T> Node_link1 = this.headNode;

    // 这里是从第一个有效节点开始-- 进行遍历操作 -- 记录当前遍历的位置
    Node<T> Node_link2 = sll.headNode.next; 

    // 如果实参无有效节点,直接结束方法
    if(Node_link2 == null) {
   
     
        return;
    }

    // 表示当前节点的状态值,如果为null,说明已经遍历完毕
    while(Node_link1 != null) {
   
     

        // 存储 当前节点的下一个节点位置
        Node<T> next_Node_link1 = Node_link1.next;

        // 如果本地链表已经遍历完毕,则直接将剩下的实参节点拼接即可 - 退出方法
        if(next_Node_link1 == null ) {
   
     
            Node_link1.next = Node_link2;
            return;
        }

        // 我们需要找出原链表插入节点的前一个节点
        if(next_Node_link1 != null) {
   
     

            // 原果原链表的下一个节点 大于  实参的当前有效节点 ,则直接插入到原链表
            if( Node_link1.next.data.hashCode() > Node_link2.data.hashCode() ) {
   
     

                // 存储实参链表的下一个节点
                Node<T> next_Node_link2 = Node_link2.next;
                // 进行实参当前节点在原链表的插入操作
                Node_link2.next = next_Node_link1;
                Node_link1.next = Node_link2;
                // 因为插入了新节点,则原来的新节点下一个元素应指向插入节点
                next_Node_link1 = Node_link2;
                // 转到下一个节点
                Node_link2 = next_Node_link2;
            }
        }

        // 原链表转到下一个节点
        Node_link1 = next_Node_link1;
    }
}

测试代码

public static void main(String[] args) {
   
     

    // 1. 打印有序链表1
    Student stu1 = new Student("lrc", 22);
    Student stu2 = new Student("glw", 27);
    Student stu3 = new Student("yxe", 25);
    SingleLinkedList<Student> sll = new SingleLinkedList<Student>();
    sll.addOrder(stu1);
    sll.addOrder(stu2);
    sll.addOrder(stu3);
    System.out.println(sll);
    System.out.println("\n---------------------------\n");

    // 2. 打印有序链表2
    Student stu4 = new Student("lrc", 21);
    Student stu5 = new Student("glw", 250);
    Student stu6 = new Student("yxe", 28);
    SingleLinkedList<Student> sll2 = new SingleLinkedList<Student>();
    sll2.addOrder(stu4);
    sll2.addOrder(stu5);
    sll2.addOrder(stu6);
    System.out.println(sll2);
    System.out.println("\n---------------------------\n");

    // 3. 将有序链表2 合并到 有序链表1 ,并将其打印
    sll.mergeOrder(sll2);
    System.out.println(sll);

}

运行结果