数据结构与算法--链表
简介
,其物理结构不能表示数据元素的逻辑顺序,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列的结点(链表中的每一个元素称为结点)组成,结点可以在运行时动态生成
节点类设计
public class Node<T>{
/**存储元素*/
public T item;
/**指向下一个结点*/
public Node next;
public Node(T t , Node next){
this.item = t;
this.next = next;
}
}
单向链表
简介
单向链表是由多个结点组成,每个结点都由一个数据域和一个指针域组成,数据域用来存储数据, 指针域用来指向其后继结点。链表的头结点的数据域不存储数据,指针域指向第一个真正存储数据的结点
实现
根据单链表的描述,为该单链表提供获取链表中元素的个数、链尾插入元素、指定位置插入元素、删除指定位置的元素并返回删除结点的值、获取指定位置的数据元素值、提供forEach循环遍历链表的方式
import java.util.Iterator;
public class LinkList<T> implements Iterable<T>{
/**记录首节点*/
private final Node head;
/**记录单链表中的元素个数*/
private int n;
public LinkList(){
this.head = new Node(null,null);
this.n = 0;
}
/**
* 清空单链表
*/
public void clear(){
head.next = null;
n = 0;
}
/**
* 判断单链表是否为空
* @return 为空返回true,否则返回false
*/
public boolean isEmpty(){
return head.next == null;
}
/**
* 返回单链表中的元素个数
* @return n
*/
public int length(){
return n;
}
/**返回单链表中第i个位置的节点的值*/
public T get(int i) throws Exception {
if (i < 0 || i > n){
throw new Exception("位置不合法");
}
//获取头节点
Node n = head;
//遍历寻找第i个节点
for (int index = 0; index < i; index++){
n = n.next;
}
//返回第i个节点的值
return n.item;
}
/**先单链表末尾插入一个新结点*/
public void insert(T t){
//获取头节点
Node node = head;
//遍历循环到后继节点
while (node.next != null) {
node = node.next;
}
//新建一个节点
Node newNode = new Node(t,null);
//插入
node.next = newNode;
//元素个数加1
n++;
}
/**在链表的第i个元素之前插入一个值为t的数据元素 */
public void insert(int i,T t) throws Exception {
if (i < 0 || i > n) {
throw new Exception("位置不合法!");
}
//获取头节点
Node node = head;
//遍历到第i个节点的前一个节点
for (int index = 0; index <= i-1;index++){
node = node.next;
}
//第i个节点
Node cur = node.next;
//新建一个节点,将新建的节点指向第i个节点
Node newNode = new Node(t,cur);
//将第i个节点的前一个节点指向新建的节点
node.next = newNode;
//元素个数加1
n++;
}
/**删除并返回链表中第i个数据元素*/
public T remove(int i) throws Exception {
if (i < 0 || i > n) {
throw new Exception("位置不合法!");
}
//获取头节点
Node node = head;
//遍历找到第i个节点的前一个节点
for (int index = 0; index <= i-1; index++){
node = node.next;
}
//第i个节点
Node cur = node.next;
//删除第i个节点
node.next = cur.next;
//元素个数减一
n--;
//返回删除的第i个节点
return cur.item;
}
/**返回链表中首次出现的指定的数据元素的位序号,若不存在,则返-1*/
public int indexOf(T t){
//获取头节点
Node node = head;
//遍历寻找
for (int index = 0; index < n; index++){
node = node.next;
if (node.item.equals(t)){
return index;
}
}
return -1;
}
@Override
public Iterator<T> iterator() {
return new MyIterator();
}
public class MyIterator implements Iterator{
private Node node;
public MyIterator(){
this.node = head;
}
@Override
public boolean hasNext() {
return node.next!=null;
}
@Override
public Object next() {
node = node.next;
return node;
}
}
private class Node{
/**存储元素*/
public T item;
/**记录下一个节点*/
public Node next;
public Node(T t,Node next){
this.item = t;
this.next = next;
}
}
}
缺点
- 待改进的地方:添加一个last结点的成员变量,用于记录单链表的尾结点,方便尾插入时不用再次的全链表的扫描到链表尾部,可以直接在尾部进行插入即可
-
-
双向链表
简介
双向链表由多个结点组成,每个结点都由一个数据域和两个指针域组成,数据域用来存储数据,其中一个指针域用来指向其后继结点,另一个指针域用来指向前驱结点。链表的头结点的数据域不存储数据,指向前驱结点的指针域值为null,指向后继结点的指针域指向第一个真正存储数据的结点
节点类的设计
public class Node{
/**存储数据*/
T item;
/**指向下一个节点*/
Node next;
/**指向上一个节点*/
Node pre;
public Node(T item,Node pre,Node next){
this.item = item;
this.pre = pre;
this.next = next;
}
}
实现
根据双向链表的描述,为该链表提供获取链表中元素的个数、链尾插入元素、指定位置插入元素、删除指定位置的元素并返回删除结点的值、获取指定位置的数据元素值、提供forEach循环遍历链表等
package linear;
import java.util.Iterator;
public class TowWayLinkList<T> implements Iterable<T>{
/**首节点*/
private final Node head;
/**尾节点*/
private Node last;
/**双向链表中元素的个数*/
private int n;
public TowWayLinkList(){
//初始化尾节点和头节点
this.last = null;
this.head = new Node(null,null,null);
//初始化元素个数
this.n= 0;
}
/**清空双向链表*/
public void clear(){
this.last = null;
this.head.pre = null;
this.head.item = null;
this.head.next = null;
this.n = 0;
}
/**判断双向链表是否为空*/
public boolean isEmpty(){
return this.n == 0;
}
/**返回双向链表的长度*/
public int length(){
return this.n;
}
/**获取第一个节点的值*/
public T getFirst(){
if (isEmpty()){
return null;
}
return head.next.item;
}
/**获取尾节点的值*/
public T getLast(){
if (isEmpty()) {
return null;
}
return last.item;
}
/**获取第i个位置的值*/
public T get(int i) throws Exception {
if (i < 0 || i >= this.n) {
throw new Exception("位置错误");
}
Node n = head;
for(int index = 0;index < i;index++){
n = n.next;
}
return n.item;
}
/**向链表中插入一个节点*/
public void insert(T t){
//如果链表为空
if (isEmpty()) {
this.last = new Node(t,head,null);
this.head.next = last;
}else {
//如果链表不为空
Node oldLast = last;
Node node = new Node(t, oldLast, null);
oldLast.next = node;
this.last = node;
}
//元素个数加1
this.n++;
}
/**在链表的第i个元素之前插入一个值为t的数据元素*/
public void insert(int i,T t) throws Exception {
if (i < 0 || i > n-1){
throw new Exception("位置错误");
}
Node n = head;
//寻找位置i前一个节点
for (int index = 0; index < i; index++){
n = n.next;
}
//第i个位置的节点
Node curr = n.next;
//构造新结点
Node node = new Node(t,n,curr);
curr.pre = node;
n.next = node;
//元素个数加1
this.n++;
}
/**移除位置i的节点*/
public T remove(int i) throws Exception {
if (i<0 || i>=n){
throw new Exception("位置不合法");
}
Node n = head;
//寻找位置i前一个节点
for(int index = 0; index < i; index++){
n = n.next;
}
//获取第i位置的节点
Node oldNode = n.next;
//获取第i位置的节点后的节点
Node node = oldNode.next;
n.next = node;
node.pre = n;
//元素个数减1
this.n--;
return oldNode.item;
}
/**返回链表中首次出现的指定的数据元素的位序号*/
public int indexOf(T t){
Node node = head;
for (int index = 0; node.next != null; index++){
node = node.next;
if (node.item.equals(t)){
return index;
}
}
return -1;
}
/**循环遍历*/
@Override
public Iterator<T> iterator() {
return new MyIterator();
}
private class MyIterator implements Iterator{
private Node node = head;
@Override
public boolean hasNext() {
return node.next!=null;
}
@Override
public Object next() {
node = node.next;
return node.item;
}
}
private class Node{
/**存储数据*/
T item;
/**指向下一个节点*/
Node next;
/**指向上一个节点*/
Node pre;
public Node(T item,Node pre,Node next){
this.item = item;
this.pre = pre;
this.next = next;
}
}
}
链表跟顺序表的比较
相比较顺序表,链表插入和删除的时间复杂度虽然一样,但仍然有很大的优势,因为链表的物理地址是不连续的, 相比较顺序表,链表的查询操作性能会比较低。因此,如果在程序中查询操作比较多,建议使用顺序表,增删操作比较多,建议使用链表