引言
学习了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;
}
}
提交结果:
二、删除链表的倒数第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;
}
}
提交结果: