数据结构与算法_01链表
链表
参考学习书籍《图解数据结构—使用Java》
0、章节重点整理
-
以连接的方式表示一串数据有何好处(链表的优点)
可以共享某些空间或子表,避免空间浪费
加入或者删除节点十分容易,只需要移动指针即可
不用实现预留大的连续内存空间,可以动态链接节点 -
试说明循环链表的优缺点
优点:
循环链表在回收到可用内存空间序列及进行多项式相加运算时,较快且有效
加入或者删除节点的运算也优于一般环形链表
缺点
循环链表必须花费额外的空间来存储链接,在读取或者寻找列表中任一节点的时间与程序都比环形链表逊色
删除节点时,必须花费额外的时间找到最后的一个节点,才可以链接新表的第一个节点。 -
数组法和链表法表示稀疏矩阵有哪些优缺点,如果使用链表表示时,回收到AVL列表(可用内存空间列表),时间复杂度是多少?
数组法
省空间
非零项改动时要大量移动
链表法
改动时不需要大量移动
叫浪费空间
O(m+n+j)
m n 为行列数
j 为非零项 -
试比较双向链表与单项链表的优缺点
优点
因为双向链表有两个指针分别指向节点本身前后那两个节点,所以能够很轻松的找到它的前后节点,同时从列表的任一节点也可以找到其他节点而不需要经过反转或者比较节点等处理,执行速度较快。另外如果有任一节点的链接断裂,可以轻易的由反方向遍历列表,快速完整的重建链表
缺点
由于他有两个链接,所以在加入节点或者删除节点都得花费更多的时间去移动指针,且双向的链表更加浪费空间。另外在双向链表与单向链表的算法中,双向链表在加入一个节点的时候需要改变4个指针,二删除一个节点也要改变两个指针。不过单向链表中加入节点要改变两个指针,而删除节点只需要改变一个指针。
1、单向链表
单向链表的尾插法
//尾插法,只在尾部last插入数据
public void insert(int data, String name, int np){
Node node = new Node(data, np, name);
if(this.isEmpty()){
first = node;
last = node;
}else {
last.next= node;
last = node;
}
}
单向链表的结点删除
- 删除列表的第一个结点只要把列表的首指针指向第二个结点即可
- 删除列表的最后一个结点只需要将最后一个结点的指针直接指向null 即可
- 删除列表内的中间结点需要将欲删除的结点的指针指向其下一个结点即可
public void delete(Node delNode){
Node newNode;
Node tmp;
if(first.data == delNode.data){//删除头结点
first = first.next;
}else if (last.data == delNode.data){//删除尾结点
newNode = first;
while (newNode.next != last){
newNode = newNode.next;
}//此时newNode是last的前一个结点
newNode.next = last.next; //null可以吗???
last = newNode;
}else{ //删除中间结点
newNode = first;
tmp = first;
while (newNode.data != delNode.data){
tmp = newNode;
newNode = newNode.next;
}//tmp始终都是newNode的前一个结点
//此时newNode是要删除的,直接把前一个就是tmp直接指向newNode的下一个
tmp = newNode.next;
}
}
单向链表的结点插入
- 在列表的第一个结点之前,只需要把新节点newNode 的指针移向表头first ,再把表头first 移向新节点即可
- 在列表的最后一个结点后面插入节点,把列表的最后一个结点last 的指针指向新节点,新节点指向null 即可
- 在列表的中间结点位置插入节点,如果插入的节点在X 和Y 之间,只需要将X 节点的指针指向新节点,新节点的指针指向Y 节点即可
public void insert(Node ptr){
Node tmp;
Node newNode;
if(this.isEmpty()){
first = ptr;
last = ptr;
}else {
if(ptr.next == first){//插入到第一个结点
ptr.next = first;
first = ptr;
}else if(ptr.next == null){//插入到最后
last.next = ptr;
last = ptr;
}else {//插入到中间结点
tmp = first;
newNode = first;
while(ptr.next != newNode){
tmp = newNode;
newNode = newNode.next;
}
tmp.next = ptr;
ptr.next = newNode;
}
}
}
链表的反转
//链表的反转
public Node reverse(LinkedList oldList){
Node current = first;
Node before = null;
while(current != null){
last = before;
before = current;
current = current.next;
before.next = last;
}
current = before;
return current;
}
单向链表的串联
//单向链表的串联
public LinkedList concatenate(LinkedList head1, LinkedList head2){
LinkedList ptr;
ptr = head1;
while ((ptr.last.next != null)) {
ptr.last = ptr.last.next;
}
ptr.last.next = head2.first;
return head1;
}
2、环形链表
//判断链表是都为空
public boolean isEmpty(){
return first == null;
}
环形链表的结点插入
- 直接将新节点插在第一个节点前成为表头
- 将新节点的指针指向原表头
- 找到原表的最后一个节点,并将指针指向新节点
- 将表头指向新节点
- 将新节点I 插在任意节点X 之后
- 将新节点I 的指针指向X 节点的下一个节点
- 将X 节点的指针指向I 节点
public void insert(Node trp){
Node tmp;
Node newNode;
if(this.isEmpty()){ //插入第一个位置,空的环形链表
first = trp;
last = trp;
last.next = first;
}else if(trp.next == null){ //trp.next为空 插入是最后一个
last.next = trp;
last = trp;
trp.next = first;
}else { //中间某一个位置
newNode = first;
tmp = first;
while (newNode.next != trp.next){
if(tmp.next == first){
break;
}
tmp = newNode;
newNode = newNode.next;
}
tmp = newNode;
trp.next = newNode;
}
}
环形链表的节点删除
- 删除环形链表的第一个节点
- 将表头head 移到下一个节点
- 将最后一个节点的指针移到新节点
- 删除环形链表的中间节点
- 先找到要删除的节点X 的前一个节点
- 将X 节点的前一个节点的指针指向节点X 的下一个节点
//删除某一个节点
public void delete(Node delNode){
Node newNode;
Node tmp;
if(this.isEmpty()){
System.out.println("环形链表空");
return;
}
if(first.data == delNode.data){
first = first.next;
if(first == null){
System.out.println("环形链表空");
return;
}
}else if(last.data == delNode.data){
newNode = first;
while (newNode.next != last){
newNode = newNode.next;
}
newNode.next = last.next;
last = newNode ;
last.next = first;
}else {
newNode = first;
tmp = first;
while (newNode.data != delNode.data){
tmp = newNode;
newNode = newNode.next;
}
tmp.next = delNode.next;
}
}
3、双向链表
public class Node {
int data;
Node rnext;
Node lnext;
public Node(int data) {
this.data = data;
this.rnext = null;
this.lnext = null;
}
}
构造
左链接LLink 数据Data 右链接RLink
双向链表的节点插入
-
将新节点插入到列表的第一个节点前
- 将新节点的右链接RLink 指向原表的第一个节点
- 将原表的第一个节点的左链接LLink 指向新节点
- 将原表的表头head 指向新节点且新节点的左链接指向null
-
将新节点加入到此表的最后一个节点之后
- 将原表的最后一个节点的右链接指向新节点
- 将新节点的左链接指向原表的最后一个节点并将新节点的右链接指向null
-
将新节点加入到ptr 节点之后
- 将ptr 节点的右链接指向新节点
- 将新节点的左链接指向ptr 节点
- 将ptr 节点的下一个节点的左链接指向新节点
- 将新节点的右链接指向ptr 的下一个节点
//插入节点
public void insert(Node newN){
Node tmp;
Node newNode;
if(this.isEmpty()){
first = newN;
first.rnext = last;
last = newN;
last.lnext = first;
}else {
if(newN.lnext == null){//插入表头位置
first.lnext = newN;
newN.rnext = first;
first = newN;
}else if(newN.rnext == null){//插入表尾的位置
last.rnext = newN;
newN.lnext = last;
last = newN;
}else {//插入中间结点为位置
newNode = first;
tmp = first;
while (newN.rnext != newNode.rnext){
tmp = newNode;
newNode = newNode.rnext;
}
tmp.rnext = newN;
newN.rnext = newNode;
newNode.lnext = newN;
newN.lnext = tmp;
}
}
}
双向链表节点的删除
- 删除表的第一个节点
- 将表头指针head 指到原表的第二个节点
- 将新的表头指针指向NULL
- 删除最后一个节点
- 将原表的最后一个节点之前的一个节点的右链接指向NULL即可
- 删除ptr 节点
- 将ptr 节点的前一个节点的右链接指向ptr 节点的下一个节点
- 将ptr 下一个节点的左链接指向ptr 节点的上一个节点
//删除节点
public void delete(Node delNode){
Node newNode;
Node tmp;
if(this.isEmpty()){
System.out.println("表是空的,无需删除。");
return;
}
if(delNode == null){
System.out.println("删除节点输入错误。");
return;
}
if(first.data == delNode.data){//删除节点是表头
first = first.rnext;
first.lnext = null;
}else if(last.data == delNode.data){//删除节点是表尾
newNode = first;
while (newNode.rnext != last){
newNode = newNode.rnext;
}
newNode.rnext = null;
last = newNode;
}else {//删除中间节点
newNode = first;
tmp = first;
while (newNode.data != delNode.data){
tmp = newNode;
newNode = newNode.rnext;
}
tmp.rnext = delNode.rnext;
tmp.lnext = delNode.lnext;
}
}