数据结构与算法(上) Datawhale组队学习——顺序表和链表

引言

学习了JAVA的基础知识以后,这是本人第一次接触数据结构与算法的知识,为了更高效以及自律的学习,我加入了Datawhale学习小组


Task02------顺序表和链表


理论部分:


一、理解线性表的定义与操作

1.线性表的定义

线性表是逻辑概念,只要所有的数据在逻辑上是一维的都可以认为是线性表。这意味着顺序表、链表都是线性表。
线性表中元素的个数称为 “表长”

2.线性表的特征
  • 集合中必存在唯一的一个“第一元素”。
  • 集合中必存在唯一的一个 “最后元素” 。
  • 除最后一个元素之外,均有唯一的后继(后件)。
  • 除第一个元素之外,均有唯一的前驱(前件)。
3.线性表的基本操作
  • 增、删、改、查
  • 随机存取
  • 清空+判断是否为空
  • 获得表长

二、顺序表:利用顺序存储结构实现的线性表

1.顺序表特点

绘制不易

  • 按顺序储存结构储存其实就是数组的储存方式。
  • 顺序表的逻辑结构与物理结构相同
  • 支持随机读取 (查找元素)
  • 不支持随机存储 (添加、删除元素),只能顺序存储
2.实现顺序表

实际上顺序表可以看做是拥有线性表功能(实现了线性表接口)的泛型数组
具体实现可以参考上期动态数组的实现,这里只简单列举属性和方法

class SeqList<T>{
	//字段
	private T[] dataset=null;
	//属性
	private int length;
	//构造方法
	SeqList(int max_length){}
	//方法(部分)
	public void add(int index,T data){}
	public void remove(int index){}
	public void clear(){}
	public boolean isEmpty(){}
	public int search(T data){}
}

三、链表:利用链式存储结构实现的线性表

1.链表特点

绘制不易

  • 链表的逻辑结构与物理结构不同,数据元素可以随机存放
  • 由若干结点(Node)组成,结点由数据域(Data)和指针域(previous,next)组成
  • 支持随机存储
  • 不支持随机读取,只能顺序读取
2.实现单链表

单链表即单向链表,Node结点类 中只有指向下一个结点的引用 next,SLinkList中还有一个标志头结点的量 first
绘制不易

i>Node结点类
class Node<T>{
	public T Data;	//数据域
	public Node<T> next;//指针域,指向下一个结点
	Node(T Data,Node<T> next){
		this.Data=Data;
		this.next=next;
	}
}
ii>SLinkedList单链表类

基本框架以及基本功能的实现

class SLinkedList{
	private Node<T> first;//用于标记头结点
	private int size;//链表长度
	public int getSize(){
		return size;
	}
	SLinkedList(){
		first=null;
		size=0;
	}
	/**
	*将元素插入头部,从后往前添加元素
	*@param Data:插入的数据
	*/
	public void addFirst(T Data){
		first=new Node<T>(Data,first);
		size++;
	}
	/**
	*将元素插入尾部,从前往后添加元素
	*@param Data:插入的数据
	*/
	public void addLast(T Data){
		Node<T> key=new Node<T>(Data,null);
		if(first==null)
			first=key;
		else{
			Node<T> tmp=first;
			while(tmp.next!=null)
				tmp=tmp.next;
			tmp.next=key;
		}
		size++;
	}
	/**
	*移除对应下标的元素(偷个懒,只考虑下标存在)
	*@param index:存在的下标
	*/
	public void remove(int index){ 
		Node<T> previous=null;	//由于是单向链表,需要previous记录前一个结点
		Node<T> current=first;	//current记录待删除的结点
		for(int i=0;i<index;i++){
			previous=current;
			current=current.next;
		}
		if(current==first)
			first=first.next;
		else
			previous.next=current.next;
		size--;
	}
	//显示链表
	public void displayList(){
		Node<T> tmp=first;
		while(tmp.next!=null){
			System.out.print(tmp.Data+" -> ");
			tmp=tmp.next;
		}
		System.out.print(tmp.Data+" -> null");
	}
}
3.实现循环列表

循环链表在单链表的基础上增加了标志尾结点的量 last,并使 last.next 指向 first,形成循环
绘制不易

i>Node结点类(与单链表相同)
ii>CLinkedList循环链表类(只展示不同处)
class CLinkedList<T>{
	private Node<T> first;//用于标记头结点
	private Node<T> last;//用于标记尾结点
	private int size;
	CLinkedList(){
		first=null;
		last=null;
		size=0;
	}
	/**
	*将元素插入头部,从后往前添加元素
	*@param Data:插入的数据
	*/
	public void addFirst(T Data){
		if(first==null)
			first=last=new Node<T>(Data,first);
		else{
			first=new Node<T>(Data,first);
			last.next=first;
		}
		size++;
	}
	/**
	*将元素插入尾部,从前往后添加元素
	*@param Data:插入的数据
	*/
	public void addLast(T Data){
		if(last==null)
			last=first=new Node<T>(Data,null);
		else{
			last.next=new Node<T>(Data,null);
			last=last.next;
			last.next=first;
		}
		size++;
	}
	/**
	*移除对应下标的元素(偷个懒,只考虑下标存在)
	*@param index:存在的下标
	*/
	public void remove(int index){ 
		Node<T> previous=null;	//由于是单向链表,需要previous记录前一个结点
		Node<T> current=first;	//current记录待删除的结点
		for(int i=0;i<index;i++){
			previous=current;
			current=current.next;
		}
		if(current==first)
			first=first.next;
		else if(current==last){
			previous.next=last.next;
			last=previous;
		}
		else
			previous.next=current.next;
		size--;
	}
	//显示链表
	public void displayList(){
		Node<T> tmp=first;
		while(tmp!=last){
			System.out.print(tmp.Data+" -> ");
			tmp=tmp.next;
		}
		System.out.print(tmp.Data+" -> null");
	}
}
4.实现双向链表

单链表和循环链表都可以很方便的到达下一个结点,然而没有对应的方法回到前一个结点,这个限制可能引起不便(例如删除结点)。双向链表在 Node结点类 中增加指向前一结点的引用 previous,这样一来,便可以随意向前或向后遍历链表。
绘制不易

i>Node结点类
class Node<T>{
	public T Data;	//数据域
	public Node<T> previous;//指针域,指向前一个结点
	public Node<T> next;//指针域,指向后一个结点
	Node(Node<T> previous,T Data,Node<T> next){
		this.previous=previous;
		this.Data=Data;
		this.next=next;
	}
}
ii>DLinkedList双向链表类(非循环)
class DLinkedList<T>{
	private Node<T> first;//用于标记头结点
	private Node<T> last;//用于标记尾结点
	private int size;
	DLinkedList(){
		first=null;
		last=null;
		size=0;
	}
	
	public void getFirst() {
		System.out.print(first.Data+" -> null");
	}
	
	public void getLast() {
		System.out.print(last.Data+" -> null");
	}
	
	/**
	*将元素插入头部,从后往前添加元素
	*@param Data:插入的数据
	*/
	public void addFirst(T Data) {
		if(first==null)
			first=last=new Node<T>(null,Data,first);
		else {
			first=new Node<T>(null,Data,first);
			first.next.previous=first;
		}
		size++;
	}
	/**
	*将元素插入尾部,从前往后添加元素
	*@param Data:插入的数据
	*/
	public void addLast(T Data){
		if(last==null)
			last=first=new Node<T>(last,Data,null);
		else {
			last=new Node<T>(last,Data,null);
			last.previous.next=last;
		}
		size++;
	}
	/**
	*移除对应下标的元素(偷个懒,只考虑下标存在)
	*@param index:存在的下标
	*/
	public void remove(int index){ 
		Node<T> current=first;	//current记录待删除的结点
		for(int i=0;i<index;i++)
			current=current.next;
		
		if(current==first) {
			first=first.next;
			first.previous=null;
		}
		else if(current==last) {
			last=last.previous;
			last.next=null;
		}
		else {
			current.previous.next=current.next;
			current.next.previous=current.previous;
		}
		size--;
	}
	//显示链表
	public void displayList(){
		Node<T> tmp=first;
		while(tmp!=last){
			System.out.print(tmp.Data+" -> ");
			tmp=tmp.next;
		}
		System.out.print(tmp.Data+" -> null");
	}
}

练习部分:


一、合并两个有序链表(LeetCode第21题)

1.要求

将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
2.分析
  • 定义一个result结点以及一个能够改变result的 tmp结点
  • 依次比较 l1 与 l2 的数据,用 tmp.next 装载较小的数据,然后将 l1 或 l2 后移
  • 最后 l1 或 l2 必有一个先为空,意味着非空的结点的数值一定最大
  • 将 tmp.next 与剩下的大数值结合,再返回 result.next 即可
3.代码实现
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode res=new ListNode(0);//这个结点在所有结点之前
        ListNode tmp=res;
        while(l1!=null && l2!=null){
            if(l1.val<l2.val){
                tmp.next=l1;
                l1=l1.next;
            }
            else{
                tmp.next=l2;
                l2=l2.next;
            }
            tmp=tmp.next;
        }
        tmp.next=(l1==null)?l2:l1;//本来用的使if-else语句,结果很消耗内存...
        return res.next;
    }
}

提交结果:
内存不知道怎么减少55555


二、删除链表的倒数第N个节点(LeetCode第19题)

1.要求

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

示例:

给定一个链表: 1->2->3->4->5, 和 n = 2.

当删除了倒数第二个节点后,链表变为 1->2->3->5.
2.分析
  • 采用双指针,previous 指向待删除元素的前一个元素,current 指向当前访问的元素
  • 确保两个指针间距离为n,当 current 到达末尾时,previous 也随之到位
  • 判断删除的元素是否为 head,做出相应的删除操作
3.代码实现
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode previous=new ListNode(0);
        previous.next=head;
        ListNode current=head; //用来遍历结点
        for(int i=1;current!=null;i++){
            current=current.next;
            if(i>n)	//确保两指针间距为n
                previous=previous.next;
        }
        if(previous.next==head)
            head=head.next;
        else
            previous.next=previous.next.next;
        return head;
    }
}

提交结果:
执行用时还能怎么提高???


三、旋转链表(LeetCode第61题)

1.要求

给定一个链表,旋转链表,将链表每个节点向右移动k个位置,其中k是非负数。

示例 1:

输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL

解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL

示例 2:

输入: 0->1->2->NULL, k = 4
输出: 2->0->1->NULL

解释:
向右旋转 1 步: 2->0->1->NULL
向右旋转 2 步: 1->2->0->NULL
向右旋转 3 步: 0->1->2->NULL
向右旋转 4 步: 2->0->1->NULL
2.分析
  • 由于旋转长度可能超过表长,所以必须先获得表长
  • “旋转”操作可以先将链表做成循环链表,然后在指定结点断开
3.代码实现
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        int len=1;
        ListNode tmp=head;
        if(head==null)	//防止输入为空
            return head;
        else
            while(tmp.next!=null){
                len++;
                tmp=tmp.next;
            }
        tmp.next=head;//形成循环链表
        tmp=head;//重新遍历,找到“旋转”的结点
        for(int i=1;i<len-k%len;i++)
            tmp=tmp.next;
        head=tmp.next;
        tmp.next=null;
        return head;
    }
}

提交结果:
舒服了,舒服了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值