声明:码字不易,转载请注明出处,欢迎文章下方讨论交流。
前言:Java数据结构与算法专题会不定时更新,欢迎各位读者监督。本文介绍另一种数据结构——链表,包括链表的特点特点、链表的创建、删除、插入和输出,文末给出java代码和一道常见的关于链表的面试题。
1、链表的概念和特点
链表是由若干结点组成,每个结点至少包括两部分信息:一个是元素数据,一个是指向下一个(上一个)元素地址的指针。链表的存储在物理上是非连续、非顺序的存储结构,数据元素之间是通过每个元素的指针来关联的。
与数组相比,链表独特的存储结构克服了数组提前需要设置长度的缺点,在运行时可以动态的快速的添加和删除元素;计算机的存储空间并非连续的,而链表则可以灵活的使用存储空间,能更好的对计算机内存进行动态管理。
链表分为单链表、双链表、和循环链表,双链表的每个结点有两个指针,分别指向前一个元素和后一个元素,循环链表的尾结点指针不是null,而是指向头结点元素的地址。
2、链表的操作
链表的操作包括了创建、删除、插入、输出。
创建就是空间的分配,将头、尾指针及链表结点个数等初始化。
删除和插入根据被操作元素的位置可以细分为头删除(插入),尾删除(插入),中间删除(插入),以下详细介绍。
创建和输出比较简单,不做具体分析,后面直接给出代码
2.1 插入操作
插入分为头插入,尾插入,中间插入
头插入
头插入实际上是增加一个新节点,然后把新增加的结点指针指向原来头指针指向的元素,再把头指针指向新增的节点。
尾插入
尾插入也是增加一个新节点,该节点指针置为null,然后把原尾结点指针指向新增加的节点,最后把尾指针指向新增加的节点即可。
中间插入
中间插入稍复杂,首先增加一个节点,然后新增节点的指针指向插入位置的后一个节点,把插入位置的前一个节点指针指向新插入节点即可。
2.2 删除操作
删除与插入类似,根据被操作元素的位置分为头删除,尾删除,中间删除
头删除
删除头元素时,先将头指针指向下一个节点,然后把原头结点的指针置空即可
尾删除
删除尾元素时,首先找到链表倒数第2个元素,然后把尾指针指向这个元素,接着把原倒数第2个元素的指针置空。
中间删除
删除中间元素相对复杂一些,首先将要删除的节点的前一个节点指针指向要删除的节点的下一个节点,然后把要删除节点的指针置空。
3、java代码实现
链表是由一系列节点组成,首先是节点部分
public class Node {
private int data; //数据
private Node next; //指针
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
链表节点部分的代码实现了两个部分,一个是数据,一个是指向下一个节点的指针(由于java中摒弃了c++中的指针概念,准确的说应该是引用)
以下是链表的代码实现:
public class Link {
private int size = 0;
private Node first;
private Node last;
/*链表初始化 */
public Link(){}
/**
* 返回链表长度
* @return 返回链表长度
*/
public int getLength(){
return size;
}
/**
* 获取指定位置的节点
* @param index 位置[0~size]
* @return
*/
public Node get(int index){
Node temp = first;
for(int i=0;i<index;i++){
temp = temp.getNext();
}
return temp;
}
/**
* 链表中插入第一个元素时,头和尾时同一个元素
* @param element
*/
private void onetNode(int element){
first = new Node();
first.setData(element);
last = first;
}
/**
* 链表只剩一个节点时,清除first和last
*/
private void clear(){
first = null;
last = null;
size = 0;
}
/**
* 插入尾节点
* @param element
*/
public void addTail(int element){
if(size==0){ //链表为空时,插入尾节点即第一个节点
onetNode(element);
}else{
Node node = new Node();
node.setData(element);
last.setNext(node);
last = node; //尾节点设置为插入的节点
}
size++;
}
/**
* 链表头插入
* @param element
*/
public void addHead(int element){
if(size==0){
onetNode(element);
}else{
Node node = new Node();
node.setData(element);
node.setNext(first); //新插入元素的指针指向原头元素
first = node; //新插入的元素设为头节点
}
size++;
}
/**
* 插入中间元素,考虑头尾两种特殊情况
* @param index 位置
* @param element 值
*/
public void add(int index,int element){
if(index < size){
if(size==0){ //空链表
onetNode(element);
size++;
}else if(index==0){ //插入的位置是头
addHead(element);
size++;
}else if(size==index+1){ //插入的位置是尾
addTail(element);
size++;
}else{
Node temp = get(index); //获取插入位置的节点
Node node = new Node(); //插入新节点
node.setData(element);
node.setNext(temp.getNext());
temp.setNext(node);
size++;
}
}else{
throw new IndexOutOfBoundsException("插入位置无效或超出链表长度");
}
}
/**
* 删除头节点
*/
public void deleFirst(){
if(size==0){
throw new IndexOutOfBoundsException("空链表,无元素可删除");
}else if(size==1){ //只剩一个节点时,清除first和last
clear();
}else{
Node temp = first; //为了将删除的头结点置空
first = first.getNext();
temp = null;
size--;
}
}
/**
* 删除尾节点
*/
public void deleLast(){
if(size==0){
throw new IndexOutOfBoundsException("空链表,无元素可删除");
}else if(size==1){ //只剩一个节点时,清除first和last
clear();
}else{
Node temp = get(size-1); //获取最后元素的前一个节点(前驱)
temp.setNext(null);
size--;
}
}
/**
* 删除中间元素,考虑了头尾和超界
* @param index 位置
*/
public void deleMid(int index){
if(size==0){
throw new IndexOutOfBoundsException("空链表,无结点可删");
}else if(size==1){
clear();
}else{
if(index==0){
deleFirst();
}else if(index==size-1){
deleLast();
}else if(index>size){
throw new IndexOutOfBoundsException("删除位置超界");
}else{
Node temp = get(index-1);
temp.setNext(get(index));
temp.setNext(null);
size--;
}
}
}
/**
* 获取链表
*/
public void getAll(){
Node temp = first;
System.out.println(temp.getData());
while(temp.getNext()!=null){
System.out.print(temp.getData()+"-->");
temp = temp.getNext();
size--;
}
}
}
4、测试代码
public class LinkTest {
public static void main(String[] args) {
Link link = new Link();
link.addHead(1); //1
link.printLink();
link.addHead(5); //5->1
link.printLink();
link.addTail(9); //5->1->9
link.printLink();
link.addTail(7); //5->1->9->7
link.printLink();
link.add(3,8); //5->1->9->8->7
link.printLink();
System.out.println("链表长度:"+link.getLength()); //5
link.deleFirst(); //1->9->8->7
link.printLink();
link.deleLast(); //1->9->8
link.printLink();
link.deleMid(1); //1->8
link.printLink();
System.out.println("链表长度:"+link.getLength()); //2
}
}
5.小结
链表的操作稍稍复杂,插入和删除要考虑很多因素(边界条件、异常),写完代码要逐个测试,出现不一致的情况逐步调试、跟踪变量。记住插入和删除的步骤(六张图),编码时仔细一点一般不会出现问题。
以上代码经过测试无误,欢迎收藏转发,欢迎下方讨论交流^_^
码字——>画图——>编码——>调试 一趟下来不容易,如果对您有帮助请收藏
和点赞
更新:我的另一篇的文章Java数据结构与算法——链表面试介绍了链表的特点,使用场景、链表的性能分析以及一道经典的链表面试题——链的反转问题,请移步阅读参考。