探究数据结构之链表Java代码实现(一)

上一篇博客探究了简单递归的思想,从这篇博客起,我们要探索数据结构,先从链表开始~


链表原理

链表的内存模型

单链表有两个属性:

  • value,值
  • next,指向下一个节点的指针(引用)

双链表附加一个属性:

  • pre,指向上一个节点的指针(引用)


我们可以尝试自己写一个简单的链表:

public class ListNode<T> {

	private T value;
	private ListNode<T> next;
	public ListNode(T value, ListNode<T> next) {
		super();
		this.value = value;
		this.next = next;
	}
	public ListNode() {
		super();
		
	}
	private ListNode<T> pre;
	
}

测试类:

@Test
	public void test01() throws Exception {
		ListNode<Integer> p4 = new ListNode<Integer>(4,null);
		ListNode<Integer> p3 = new ListNode<Integer>(3,p4);
		ListNode<Integer> p2 = new ListNode<Integer>(2,p3);
		ListNode<Integer> p1 = new ListNode<Integer>(1,p2);
	}



其内存模型看起来似乎是这个样子的存储结构:



但是,实际上是这个样子的:


对于这种情况:

@Test
	public void test02() throws Exception {
		ListNode<Integer> p1=new ListNode<Integer>(1, new ListNode<Integer>(2, 
				new ListNode<Integer>(3, new ListNode<Integer>(4, null))));
	}


模拟链表

了解了其内存模型后,我们可以自己再尝试写一个链表,实现其如下基本功能:



创建和打印

我们可以根据数组array创建链表:



打印链表的思路:

依次打印链表,其实就是遍历
遍历的方法:
p=p.next


代码如下:

/**
 * 数组转换成链表
 */
public void arrayToList(T[] array){
	ListNode<T> p=head;
	for(T t:array){
		ListNode<T> node=new ListNode<T>(t, null);
		p.next=node;
		p=node;
	}
}
/**
 * 打印链表
 */
public void printList(){
	ListNode<T> p=head.next;
	while(p!=null){
		System.out.print(p.value+" ");
		p=p.next;
	}
	System.out.println();
}

插入和删除



节点的删除:

T remove(int index),删除第index个节点,并返回节点的值

  • 遍历链表,找到前趋节点pre,以及节点p
  • pre.next=p.next
  • return p.value


查询和修改

查询:

T get(int index),返回第index个节点的值,index从0开始。

修改:

void set(int index,T value),将第index个节点的值设置为value,index从0开始。


遍历!

/**
 * 查询
 */
public T get(int index){
	ListNode<T> p=head;
	for(int i=0;i<=index;i++){
		p=p.next;
	}
	return p.value;
}
/**
 * 修改
 */
public void set(int index,T value){
    ListNode<T> p=head;
    for(int i=0;i<=index;i++){
        p=p.next;
    }
    p.value=value;
}

当然,以上代码只是简单的表达思想,不够严谨。


参考LinkedList源码:

  • 容器大小:size()
  • 是否为空:isEmpty()
  • for循环的封装
  • 边界检查
  • 迭代器

ArrayList、LinkedList、Stack、Queue等线性结构的源码,以后再探讨。

逆序打印链表

还是和之前一样,我通过解决问题去学习、去加深认识。

给定单链表,从尾到头打印每个节点的值,不同的值之间用空格隔开。
比如:1->2->3->4->5
输出:5 4 3 2 1
用非递归以及递归两种算法实现。


非递归实现:

先打印尾部、后打印头部。

自然而然联想到:先进后出的栈。

  • 遍历链表,将所有的节点(值)依次压栈  
  • 依次弹栈、打印,直到栈为空



/**
 * 逆序打印链表,非递归算法
 */
public void printInverse(){
	if(head.next == null){
		return;
	}
	Stack<T> stack  = new Stack<T>();
	ListNode<T> p = head.next;
	while(p != null){
		stack.push(p.value);
		p = p.next;
	}
	while(!stack.isEmpty()){
		System.out.print(stack.pop() + " ");
	}
}

这种方法的时间、空间复杂度都为O(n)。


递归实现

思路:

void recursive(ListNode p){//对于某个节点p
	if(p!=null){
		recursive(p.next);//递归p的下一个节点
		print(p);//打印本节点p
	}
}
实现代码

/**
 * 逆序打印链表,递归算法
 */
public void printInverseRecursive(){
	if(head.next == null){
		return;
	}
	recursive(head.next);
	System.out.println();
}
private void recursive(ListNode<T> p){
	if(p!=null){
		recursive(p.next);
		System.out.println(p.value + " ");
	}
	
}

这种方法的时间、空间复杂度也都为O(n)。


链表的最大元素


对于T类型的a和b,如何比较大小?

  • Comparable接口
  • 自定义Comparator


/**
 * 比较a和b的大小
 * 如果a==b,返回0
 * 如果a<b,返回负数
 * 如果a>b,返回正数
 */
@SuppressWarnings("unchecked")
public Comparator<T> comp;

public int compare(T a,T b){
	if(comp != null){
		return compare(a, b);
	}
	else{
		Comparable<T> c = (Comparable<T>) a;
		return c.compareTo(b);
	}
}


可以通过打擂台的方式以具体实现,思路如下:

T getMax(){
	p=head.next;
	max=p.value;//临时变量max,记录最大值
	p=p.next;
	while(p!=null){
      //遍历链表,依次比较max与当前值的大小
		if(compare(p.value, max)>0){//或者≥
			max=p.value;
		}
		p=p.next;
	}
	return max;
}

实现:

/**
 * 取得链表的最大值
 */
public T getMax(){
	if(head.next==null){
		return null;
	}
	ListNode<T> p=head.next;
	T max=p.value;
	p=p.next;
	while(p!=null){
		if(compare(p.value, max)>0){
			max=p.value;
		}
		p=p.next;
	}
	return max;
}

这种放的时间复杂度为O(n),空间复杂度为O(1)


其他知识点

Comparable、Comparator广泛应用于其它数据结构,以及排序、查找等算法。



以后会深入探讨!


链表反转


问题描述:

给定单链表,反转这个单链表,并返回新的头节点。
例如,给定单链表:1->2->3->4->5->6
经过反转之后,变成:6->5->4->3->2->1
leetCode 206:Reverse Linked List
用递归、非递归两种算法实现。
假设链表的长度为n,要求两种算法的时间复杂度必须为O(N);
非递归算法的空间复杂度必须为O(1)。

非递归算法的思路

三个指针:

  • pre,前趋节点
  • p,当前节点
  • next,下一个节点

关键的四个步骤:

  1. next=p.next; //把p的下一个节点存储起来
  2. p.next=pre; //当前节点反转
  3. pre=p;   //继续往下遍历
  4. p=next;  //继续往下遍历



最后再将为上图1的节点(原本的第一个节点)的next指针域,置为空。即完成了链表反转。


非递归算法的实现


/**
 * 链表反转,非递归算法
 */
public ListNode reverseList(ListNode head){
	if(head == null || head.next == null){
		return head;
	}
	else{
		ListNode pre = head;
		ListNode p = head.next;
		ListNode next = null;//缓存当前节点的指针
		while(p!=null){
			next = p.next;
			p.next = pre;
			pre = p;
			p = next;
		}
		head.next = null;
		return pre;
		
	}
}

这种方法的时间复杂度为O(n),空间复杂度为O(1)



递归算法的思路

对于某个节点p:

  • 递归处理next
  • next.next=p
  • 返回尾部节点


递归算法的实现


/**
 * 链表反转,递归算法
 */
public ListNode reverseListRecursive(ListNode head){
	if(head == null || head.next == null){
		return head;
	}
	else
	{
		ListNode tail = recursive(head);
		head.next = null;
		return tail;
	}
	
}
public ListNode recursive(ListNode p){
	if(p.next == null){
		return p;
	}else{
		ListNode next = p.next;
		ListNode tail = recursive(next);
		next.next = p;
		return tail;
	}
}

小结:

  • 指针(引用),pre、p、next
  • p=p.next
  • xxx.next=yyy.next



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值