1.线性表的链式图。
2.头结点和头指针的异同。
头指针head
- 头指针是指链表指向第一 个结点的指针,若链表有头结点,则是指向头结点的指针。
- 头指针具有标识作用,所以常用头指针冠以链表的名字。
- 无论链表是否为空,头指针均不为空。头指针是链表的必要元素。
尾指针rear
- 同头指针一样,但是指向链表中最后一个节点,最后一个节点指向的是一个null。
头结点
- 虚拟头结点:为了操作的统一和方便而设立的,放在第一元素的结点之前,其数据域般无意义(也可存放链表的长度)有了 头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其它结点的操作就统一了。
- 真实头结点:第一个节点,也就是存储元素的一个节点。
3.数据域和指针域
4.线性表的链式存储结构LinkedList的定义。
分析:
1、单链表的插入----插入元素e。
头插法:1.当该链表是一个空链表时:
将头结点的下一跳赋给插入元素e的下一跳,然后将该元素e赋给头结点的下一跳。最后将尾指针rear移动到元素e对 应的节点。
2.当该链表是一个存在元素的链表时:
将头结点的下一跳赋给插入元素e的下一跳,然后将该元素e赋给头结点的下一跳。这里的尾指针rear则不需要移 动。
尾插法:将该元素e赋给尾指针对应的结点的下一跳,然后将尾指针rear移动到元素e对应的结点。
一般插入(中间某个位置插入):比如说在A和B中间插入一个元素。
将A的下一跳赋给元素e的下一跳,然后将元素e赋给A的下一跳,由于中间插入,尾指针rear不需要移动。
思路算法 :
1. 声明一个节点p指向链表虚拟头节点,初始化i从0开始。
2. 当i<index时,就遍历链表,让p的指针向后移动,不断指向下一个节点,i累加1。
3. 若查到末尾p为空,则说明第index个元素不存在,抛异常throw new IllegalArgumentException("插入角标非 法!")。
4. 否则查找成功,在系统生成一个空节点n。
5. 将插入元素e赋值给n.data。
6. 单链表的插入标准语句n.next=p.next; p.next=n;
public void add(int index, E e) {
if(index<0||index>size){
throw new IllegalArgumentException("插入角标非法!");
}
Node n=new Node(e,null);
if(index==0){ //头插
n.next=head.next;
head.next=n;
if(size==0){
rear=n;
}
}else if(index==size){ //尾插
rear.next=n;
rear=rear.next;
}else{ //一般插入
Node p=head;
for(int i=0;i<index;i++){
p=p.next;
}
n.next=p.next;
p.next=n;
}
size++;
}
2、单链表的删除----删除index处的元素
删头:1.当该链表只存在一个元素时:
将真实头结点的下一跳赋给虚拟头结点的下一跳。这里的尾指针rear移动到虚拟头结点处。
2.当该链表不止一个元素的时候:
将真实头结点的下一跳赋给虚拟头结点的下一跳。这里的尾指针rear不需要移动。
删尾:将尾结点的下一跳赋给尾结点上一跳的下一跳,这里的尾指针rear移动到上一个位置处。
一般删除(删除中间某个位置index处元素):
将index处的下一跳赋给index处上一跳的下一跳,这里的尾指针rear不需要移动。
思路算法:
1. 声明一个节点p指向链表的虚拟头结点,初始化i从0开始。
2. 当i<index时,就遍历链表,让p的指针向后移动,不断指向下一个结点,i累加1。
3. 若到链表末尾p为空,则说明第index个元素不存在,抛异常 。
4. 否则查找成功,将欲删除的结点p.next赋值给del。
5. 单链表的删除标准语句res=del.data; p.next=del.next; del.next=null; del=null;
6. 将del.data赋值给res,也就是要删除的元素,作为返回。
7. 释放res值。
public E remove(int index) {
if(index<0||index>=size){
throw new IllegalArgumentException("删除角标非法!");
}
E res=null; //定义一个返回值,初始值为空
if(index==0){ //删头
Node p=head.next;
res=p.data;
head.next=p.next;
p.next=null;
p=null;
if(size==1){
rear=head;
}
}else if(index==size-1){ //删尾
Node p=head;
res=rear.data;
while(p.next!=rear){
p=p.next;
}
p.next=null;
rear=p;
}else{ 一般删除
Node p=head;
for(int i=0;i<index;i++){
p=p.next;
}
Node del=p.next; //将下一跳也就是删除元素赋给del
res=del.data; //将要删除的元素具体值赋给res
p.next=del.next; //将要删除元素的下一跳给p的下一跳
del.next=null; //删除元素的下一跳清空
del=null; //删除元素清空
}
size--;
return res;
}
3、单链表的获取----index处元素。
算法思路:
1. 对index进行判断,若index<或者index>=size,抛异常throw new IllegalArgumentException("查找角标非法!");。
2. 如果index==0,直接返回头结点下一跳元素,也就是return head.next.data;。
3. 如果index==size-1,直接返回尾指针对应的元素,也就是return rear.data;。
4.如果index在链表中间的某元素,创建一个Node类型的p变量,将头结点的地址给p。然后定义一个for循环,将p的地址移动 到目标index处的结点。最后返回return p.data;。
public E get(int index) {
if(index<0||index>=size){
throw new IllegalArgumentException("查找角标非法!");
}
if(index==0){
return head.next.data;
}else if(index==size-1){
return rear.data;
}else{
Node p=head;
for(int i=0;i<=index;i++){
p=p.next;
}
return p.data;
}
}
4、单链表的修改----(index,e)
算法思路:
1. 对index进行判断,若index<或者index>=size,抛异常throw new IllegalArgumentException("修改角标非法!");。
2. 若index==0,直接令头结点的下一跳的数据data等于e。
3. 如果index==size-1,直接令尾指针对应的元素等于e,也就是rear.data=e;。
4. 如果角标在链表中间某位置,创建一个Node类型的变量p,等于head的地址。
5. 建立一个for循环,从0~idnex,令p到达修改元素位置,最后p.data=e;。
public void set(int index, E e) {
if(index<0||index>=size){
throw new IllegalArgumentException("修改角标非法!");
}
if(index==0){
head.next.data=e;
}else if(index==size-1){
rear.data=e;
}else{
Node p=head;
for(int i=0;i<=index;i++){
p=p.next;
}
p.data=e;
}
}
5、单链表的查找----e
1. 令未查找到元素返回初值为int index=-1;
2. 判断链表是否为空,空返回定义的初值return index;
3. 创建一个Node类型的p变量,让头指针的地址指向p。
4. 开始一个循环,让p从头结点走向尾结点,每走一步index++,然后与e判断是否相等,相等返回index当前值,不等则返回定义的初值。
public int find(E e) {
int index=-1;
if(isEmpty()){
return index;
}
Node p=head;
while(p.next!=null){
p=p.next;
index++;
if(p.data==e){
return index;
}
}
return -1;
}
6、toString方法的创建。
1. 使用StringBuilder创建一个对象sb。
2. 对单链表进行判空,空则打印"[ ]"
3. 非空,先打印"[ " 当创建一个Node类型的p变量,将头结点的地址赋给p,开启一个0~rear的循环,每次都将新的地址对应 的元素打印出来,然后元素后边加上"," 除非打印到最后一个元素后边才加上"] "
4. 最后返回sb.toString
public String toString() {
StringBuilder sb=new StringBuilder();
sb.append("LinkedList:size="+getSize()+"\n");
if(isEmpty()){
sb.append("[]");
}else{
sb.append("[");
Node p=head;
while(p.next!=null){
p=p.next;
if(p==rear){
sb.append(p.data+"]");
}else{
sb.append(p.data+",");
}
}
}
return sb.toString();
}
代码的全部实现:
package com.oupeng.p5链表;
import com.oupeng.p1线性表.List;
public class LinkedList<E> implements List<E> {
/**
* 单向链表的结点类
* */
private class Node{
E data; //数据域
Node next; //指针域
public Node(){
this(null,null);
}
public Node(E data,Node next){
this.data=data;
this.next=next;
}
@Override
public String toString() {
return data.toString();
}
}
private Node head; //指向虚拟头结点的头指针
private Node rear; //指向尾结点的尾指针
private int size; //记录元素的个数
public LinkedList(){
head=new Node();
rear=head;
size=0;
}
public LinkedList(E[] arr){
head=new Node();
rear=head;
size=0;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size==0&&head.next==null;
}
@Override
public void add(int index, E e) {
if(index<0||index>size){
throw new IllegalArgumentException("插入角标非法!");
}
Node n=new Node(e,null);
if(index==0){ //头插
n.next=head.next;
head.next=n;
if(size==0){
rear=n;
}
}else if(index==size){ //尾插
rear.next=n;
rear=rear.next;
}else{
Node p=head;
for(int i=0;i<index;i++){
p=p.next;
}
n.next=p.next;
p.next=n;
}
size++;
}
@Override
public void addFirst(E e) {
add(0,e);
}
@Override
public void addLast(E e) {
add(size,e);
}
@Override
public E get(int index) {
if(index<0||index>=size){
throw new IllegalArgumentException("查找角标非法!");
}
if(index==0){
return head.next.data;
}else if(index==size-1){
return rear.data;
}else{
Node p=head;
for(int i=0;i<=index;i++){
p=p.next;
}
return p.data;
}
}
@Override
public E getFirst() {
return get(0);
}
@Override
public E getLast() {
return get(size-1);
}
@Override
public void set(int index, E e) {
if(index<0||index>=size){
throw new IllegalArgumentException("修改角标非法!");
}
if(index==0){
head.next.data=e;
}else if(index==size-1){
rear.data=e;
}else{
Node p=head;
for(int i=0;i<=index;i++){
p=p.next;
}
p.data=e;
}
}
@Override
public boolean contains(E e) {
return find(e)!=-1;
}
@Override
public int find(E e) {
int index=-1;
if(isEmpty()){
return index;
}
Node p=head;
while(p.next!=null){
p=p.next;
index++;
if(p.data==e){
return index;
}
}
return -1;
}
@Override
public E remove(int index) {
if(index<0||index>=size){
throw new IllegalArgumentException("删除角标非法!");
}
E res=null;
if(index==0){ //头删
Node p=head.next;
res=p.data;
head.next=p.next;
p.next=null;
p=null;
if(size==1){
rear=head;
}
}else if(index==size-1){//尾删
Node p=head;
res=rear.data;
while(p.next!=rear){
p=p.next;
}
p.next=null;
rear=p;
}else{
Node p=head;
for(int i=0;i<index;i++){
p=p.next;
}
Node del=p.next;
res=del.data;
p.next=del.next;
del.next=null;
del=null;
}
size--;
return res;
}
@Override
public E removeFirst() {
return remove(0);
}
@Override
public E removeLast() {
return remove(size-1);
}
@Override
public void removeElement(E e) {
int index=find(e);
if(index==-1){
throw new IllegalArgumentException("元素不存在");
}
remove(index);
}
@Override
public void clear() {
head.next=null;
rear=head;
size=0;
}
@Override
public String toString() {
StringBuilder sb=new StringBuilder();
sb.append("LinkedList:size="+getSize()+"\n");
if(isEmpty()){
sb.append("[]");
}else{
sb.append("[");
Node p=head;
while(p.next!=null){
p=p.next;
if(p==rear){
sb.append(p.data+"]");
}else{
sb.append(p.data+",");
}
}
}
return sb.toString();
}
public boolean equals(Object obj){
if(obj==null){
return false;
}
if(obj==this){
return true;
}
if(obj instanceof LinkedList){
LinkedList<E> l=(LinkedList<E>) obj;
if(l.getSize()==getSize()){
for(int i=0;i<getSize();i++){
if(get(i)!=l.get(i)){
return false;
}
}
return true;
}
}
return false;
}
}
5.单链表结构与顺序存储结构的优缺点。
存储分配方式:
- 顺序存储结构用一段连续的存储单元依次存储线性表的数据元素。
- 单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素。
时间性能:
- 查找 顺序存储结构O(1) 单链表O(n)
- 插入和删除 顺序存储结构需要平均移动表长一半的元素,时间为O(n)
- 单链表在线出某位置的指针后,插入和删除时间仅为O(1)
空间性能:
- 顺序存储结构需要预分配存储空间,分大了,浪费,分小了容易发生上溢。
- 单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制。