数据结构(四)线性表的链式存储结构(单链表)
一、线性表的顺序存储结构的不足:线性表的顺序结构最大的缺点就是插入和删除时需要移动大量元素,这显然就需要耗费时间。原因就在于相邻两元素的存储位置也具有邻居关系。它们编号是1,2,3...n,它们在内存中的位置也是挨着的吗,中间没有空隙,当然就无法快速插入,而删除后,当中就会留出空隙,自然需要弥补。
二、线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。这就意味着,这些数据元素可以存在内存未被占用的任意位置。在链式结构中,除了要存数据元素以外,还要存储后继元素的存储地址。
三、线性表的链式存储结构的组成:把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称作指针或链。这两部分信息组成数据元素的存储映像,称为结点node。n个结点链结成一个链表,即为线性表的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表。
四、链表中第一个结点的存储位置叫做头指针,之后每一个结点,其实就是上一个的后继指针指向的位置,最后一个结点为“空”。为了更加方便地对链表进行操作,会在单链表的第一个结点前附设一个结点,称为头结点。头结点的数据域可以不存储任何信息,也可以存储如线性表的长度等附加信息,头结点的指针域存储指向第一个结点的指针。
五、单链表结构与顺序存储结构优缺点:
- 存储分配方式:顺序存储结构用一段连续的存储单元依次存储线性表的数据元素;单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素。
- 时间性能:查找时,顺序存储结构O(1)和单链表O(n);插入和删除时,顺序存储结构需要平均移动表长一半的元素O(n)而单链表直接找出某位置的指针后,插入和删除的时间仅为O()
六、单链表的C语言代码实现
#include "stdio.h" #include "string.h" #include "ctype.h" #include "stdlib.h" #include "io.h" #include "math.h" #include "time.h" #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */ typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int */ Status visit(ElemType c) { printf("%d ",c); return OK; } /* 线性表的单链表存储结构*/ typedef struct Node { ElemType data; struct Node *next; }Node; typedef struct Node *LinkList; /* 定义LinkList */ /* 初始化顺序线性表 */ Status InitList(LinkList *L) { *L=(LinkList)malloc(sizeof(Node)); /* 产生头结点,并使L指向此头结点 */ if(!(*L)) /* 存储分配失败 */ return ERROR; (*L)->next=NULL; /* 指针域为空 */ return OK; } /* 初始条件:顺序线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE */ Status ListEmpty(LinkList L) { if(L->next) return FALSE; else return TRUE; } /* 初始条件:顺序线性表L已存在。操作结果:将L重置为空表 */ /*单链表整表删除的算法思路: * 1.声明一结点p和q * 2.将第一个结点赋值给p * 3.循环: * 将下一结点赋值给q * 释放p * 将q赋值给p*/ Status ClearList(LinkList *L) { LinkList p,q; p=(*L)->next; /* p指向第一个结点 */ while(p) /* 没到表尾 */ { q=p->next; free(p); p=q; } (*L)->next=NULL; /* 头结点指针域为空 */ return OK; } /* 初始条件:顺序线性表L已存在。操作结果:返回L中数据元素个数 */ int ListLength(LinkList L) { int i=0; LinkList p=L->next; /* p指向第一个结点 */ while(p) { i++; p=p->next; } return i; } /* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) */ /* 操作结果:用e返回L中第i个数据元素的值 */ /* 1.声明一个指针p指向链表第一个结点,初始化j从1开始 * 2.当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累加1 * 3.若到链表末尾p为空,则说明第i个结点不存在 * 4.否则查找成功,返回结点p的数据。*/ Status GetElem(LinkList L,int i,ElemType *e) { int j; LinkList p; /* 声明一结点p */ p = L->next; /* 让p指向链表L的第一个结点 */ j = 1; /* j为计数器 */ while (p && j<i) /* p不为空或者计数器j还没有等于i时,循环继续 */ { p = p->next; /* 让p指向下一个结点 */ ++j; } if ( !p || j>i ) return ERROR; /* 第i个元素不存在 */ *e = p->data; /* 取第i个元素的数据 */ return OK; } /* 初始条件:顺序线性表L已存在 */ /* 操作结果:返回L中第1个与e满足关系的数据元素的位序。 */ /* 若这样的数据元素不存在,则返回值为0 */ int LocateElem(LinkList L,ElemType e) { int i=0; LinkList p=L->next; while(p) { i++; if(p->data==e) /* 找到这样的数据元素 */ return i; p=p->next; } return 0; } /* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L), */ /* 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 */ /* 1.声明一指针p指向链表头结点,初始化j从1开始 * 2.当j<i时,就遍历链表,让p的指针向后移动, 不断指向下一结点,j累加1 * 3.若到链表末尾p为空,则说明第i个结点不存在 * 4.否则查找成功,在系统中生成一个空节点s * 5.将数据元素e赋值给s->data * 6.单链表的插入标准语句s->next=p->next; p->next=s * 7.返回成功*/ Status ListInsert(LinkList *L,int i,ElemType e) { int j; LinkList p,s; p = *L; j = 1; while (p && j < i) /* 寻找第i-1个结点 */ { p = p->next; ++j; } if (!p || j > i) return ERROR; /* 第i个元素不存在 */ s = (LinkList)malloc(sizeof(Node)); /* 生成新结点(C语言标准函数) */ s->data = e; s->next = p->next; /* 将p的后继结点赋值给s的后继 */ p->next = s; /* 将s赋值给p的后继 */ return OK; } /* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) */ /* 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 */ /* 1.声明一指针p指向链表头结点,初始化j从1开始 * 2.当j<i时,就遍历链表,让p的指针向后移动, 不断指向下一结点,j累加1 * 3.若到链表末尾p为空,则说明第i个结点不存在 * 4.否则查找成功,将想要删除的结点p->next赋值给q * 5.单链表标准删除语句:p->next=q->next * 6.将q结点中的数据赋值给e,作为返回 * 7.释放q结点 * 8.返回成功 * */ Status ListDelete(LinkList *L,int i,ElemType *e) { int j; LinkList p,q; p = *L; j = 1; while (p->next && j < i) /* 遍历寻找第i个元素 */ { p = p->next; ++j; } if (!(p->next) || j > i) return ERROR; /* 第i个元素不存在 */ q = p->next; p->next = q->next; /* 将q的后继赋值给p的后继 */ *e = q->data; /* 将q结点中的数据给e */ free(q); /* 让系统回收此结点,释放内存 */ return OK; } /* 初始条件:顺序线性表L已存在 */ /* 操作结果:依次对L的每个数据元素输出 */ Status ListTraverse(LinkList L) { LinkList p=L->next; while(p) { visit(p->data); p=p->next; } printf("\n"); return OK; } /*单链表整表创建的算法思路(包括头插法和尾插法): * 1.声明一指针p和计数器变量i * 2.初始化一空链表L * 3.让L的头结点的指针指向NULL,即建立一个带头结点的单链表 * 4.循环: * 生成一新节点赋值给p * 随机生成一数字赋值给p的数据域p->data * 将p插入到头结点与一新节点之间*/ /* 随机产生n个元素的值,建立带表头结点的单链线性表L(头插法) */ void CreateListHead(LinkList *L, int n) { LinkList p; int i; srand(time(0)); /* 初始化随机数种子 */ *L = (LinkList)malloc(sizeof(Node)); (*L)->next = NULL; /* 先建立一个带头结点的单链表 */ for (i=0; i<n; i++) { p = (LinkList)malloc(sizeof(Node)); /* 生成新结点 */ p->data = rand()%100+1; /* 随机生成100以内的数字 */ p->next = (*L)->next; (*L)->next = p; /* 插入到表头 */ } } /* 随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法) */ void CreateListTail(LinkList *L, int n) { LinkList p,r; int i; srand(time(0)); /* 初始化随机数种子 */ *L = (LinkList)malloc(sizeof(Node)); /* L为整个线性表 */ r=*L; /* r为指向尾部的结点 */ for (i=0; i<n; i++) { p = (Node *)malloc(sizeof(Node)); /* 生成新结点 */ p->data = rand()%100+1; /* 随机生成100以内的数字 */ r->next=p; /* 将表尾终端结点的指针指向新结点 */ r = p; /* 将当前的新结点定义为表尾终端结点 */ } r->next = NULL; /* 表示当前链表结束 */ } int main() { LinkList L; ElemType e; Status i; int j,k; i=InitList(&L); printf("初始化L后:ListLength(L)=%d\n",ListLength(L)); for(j=1;j<=5;j++) i=ListInsert(&L,1,j); printf("在L的表头依次插入1~5后:L.data="); ListTraverse(L); printf("ListLength(L)=%d \n",ListLength(L)); i=ListEmpty(L); printf("L是否空:i=%d(1:是 0:否)\n",i); i=ClearList(&L); printf("清空L后:ListLength(L)=%d\n",ListLength(L)); i=ListEmpty(L); printf("L是否空:i=%d(1:是 0:否)\n",i); for(j=1;j<=10;j++) ListInsert(&L,j,j); printf("在L的表尾依次插入1~10后:L.data="); ListTraverse(L); printf("ListLength(L)=%d \n",ListLength(L)); ListInsert(&L,1,0); printf("在L的表头插入0后:L.data="); ListTraverse(L); printf("ListLength(L)=%d \n",ListLength(L)); GetElem(L,5,&e); printf("第5个元素的值为:%d\n",e); for(j=3;j<=4;j++) { k=LocateElem(L,j); if(k) printf("第%d个元素的值为%d\n",k,j); else printf("没有值为%d的元素\n",j); } k=ListLength(L); /* k为表长 */ for(j=k+1;j>=k;j--) { i=ListDelete(&L,j,&e); /* 删除第j个数据 */ if(i==ERROR) printf("删除第%d个数据失败\n",j); else printf("删除第%d个的元素值为:%d\n",j,e); } printf("依次输出L的元素:"); ListTraverse(L); j=5; ListDelete(&L,j,&e); /* 删除第5个数据 */ printf("删除第%d个的元素值为:%d\n",j,e); printf("依次输出L的元素:"); ListTraverse(L); i=ClearList(&L); printf("\n清空L后:ListLength(L)=%d\n",ListLength(L)); CreateListHead(&L,20); printf("整体创建L的元素(头插法):"); ListTraverse(L); i=ClearList(&L); printf("\n删除L后:ListLength(L)=%d\n",ListLength(L)); CreateListTail(&L,20); printf("整体创建L的元素(尾插法):"); ListTraverse(L); return 0; }
输出为: 初始化L后:ListLength(L)=0 在L的表头依次插入1~5后:L.data=5 4 3 2 1 ListLength(L)=5 L是否空:i=0(1:是 0:否) 清空L后:ListLength(L)=0 L是否空:i=1(1:是 0:否) 在L的表尾依次插入1~10后:L.data=1 2 3 4 5 6 7 8 9 10 ListLength(L)=10 在L的表头插入0后:L.data=0 1 2 3 4 5 6 7 8 9 10 ListLength(L)=11 第5个元素的值为:4 第4个元素的值为3 第5个元素的值为4 删除第12个数据失败 删除第11个的元素值为:10 依次输出L的元素:0 1 2 3 4 5 6 7 8 9 删除第5个的元素值为:4 依次输出L的元素:0 1 2 3 5 6 7 8 9 清空L后:ListLength(L)=0 整体创建L的元素(头插法):78 68 67 81 41 35 54 35 96 91 45 33 20 66 6 55 27 1 29 46 删除L后:ListLength(L)=0 整体创建L的元素(尾插法):46 29 1 27 55 6 66 20 33 45 91 96 35 54 35 41 81 67 68 78
七、单链表的Java语言代码实现
- Node节点类:
package bigjun.iplab.linkList; public class Node { public Integer data; // 存放结点的数据元素的数据域(int类型不能设置null,而Integer类型可以) public Node next; // 存放后继元素的引用 // 可实现初始化一个空的结点 public Node() { this(null, null); } // 可实现构造一个数据域值为指定参数值,而指针域为空的结点 public Node(Integer data) { this(data, null); } // 可实现构造一个数据域和指针域值都为指定参数的结点 public Node(Integer data, Node next) { this.data = data; this.next = next; } }
- 单链表ADT接口类:
package bigjun.iplab.linkList; public interface IList { // 判断带头结点的单链表是否为空 public boolean isListEmpty(); // 将一个已经存在的带头结点的单链表置成空表 public void clearList(); // 求带头结点的单链表的长度 public int getListLength(); // 读取带头结点的单链表中第i个结点 public int getElem(int i) throws Exception; // 在带头结点的单链表中查找值为e的结点 public int locateElem(int e); // 在带头结点的单链表中第i个结点之前插入一个值为x的结点 public void listInsert(int i, int x) throws Exception; // 删除带头结点的单链表中的第i个结点 public void listDelete(int i) throws Exception; // 输出单链表中的所有结点 public void listTraverse(); // 随机产生n个元素的值,用头插法顺序建立单链表,n为单链表的结点个数 public void createListHead(int n) throws Exception; // 随机产生n个元素的值,用尾插法顺序建立单链表,n为单链表的结点个数 public void createListTail(int n) throws Exception; }
- 单链表类:
package bigjun.iplab.linkList; import java.util.Random; public class LinkList implements IList{ public Node head; // 单链表的头指针,头指针head指向头结点,头结点的指针域指向首结点 // 单链表的构造函数 public LinkList() { // 单链表的构造函数 head = new Node(); // 调用无参数构造函数,初始化一个空的结点作为头结点 } public boolean isListEmpty() { return head.next == null; } public void clearList() { head.data = null; head.next = null; } public int getListLength() { int length = 0; Node pNode = head.next; while (pNode != null) { length++; pNode = pNode.next; } return length; } public int getElem(int i) throws Exception { Node pNode = head.next; int j = 1; while (pNode != null && j < i) { pNode = pNode.next; ++j; } if (pNode==null || j>i) { throw new Exception("第" + i + "个元素不存在"); } return pNode.data; } public int locateElem(int e) { Node pNode = head.next; int j = 0; while (pNode!=null) { j++; if (pNode.data==e) return j; pNode=pNode.next; } return 0; } public void listInsert(int i, int x) throws Exception { Node pNode = head; int j = 1; while (pNode != null && j < i) { pNode = pNode.next; ++j; } if (j>i || pNode ==null) throw new Exception("插入位置不合法"); Node sNode = new Node(x); sNode.next = pNode.next; pNode.next = sNode; } public void listDelete(int i) throws Exception { Node pNode = head; int j = 1; while (pNode.next !=null && j<i) { pNode = pNode.next; ++j; } if (pNode.next == null && j >i) { throw new Exception("删除位置不合法"); } pNode.next = pNode.next.next; } public void listTraverse() { Node node = head.next; while (node != null) { System.out.print(node.data + " "); node = node.next; } System.out.println(); } public void createListHead(int n) throws Exception { for (int i = 1; i <= n; i++) listInsert(i, new Random().nextInt(100)); } public void createListTail(int n) throws Exception { for (int i = 1; i <= n; i++) listInsert(1, new Random().nextInt(100)); } public static void main(String[] args) throws Exception { LinkList lList = new LinkList(); System.out.println("初始化后单链表的长度为:" + lList.getListLength()); for (int i = 1; i <= 5; i++) lList.listInsert(1, i); System.out.print("在表头插入1~5后:"); lList.listTraverse(); System.out.println("在表头插入1~5后,此时单链表的长度为:" + lList.getListLength()); lList.clearList(); System.out.println("清空线性表后,此时单链表的长度为:" + lList.getListLength()); for (int i = 1; i <= 10; i++) lList.listInsert(i, i); System.out.print("在表尾插入1~10后:"); lList.listTraverse(); System.out.println("在表头插入1~10后,此时单链表的长度为:" + lList.getListLength()); lList.listInsert(1, 0); System.out.print("在表头插入0后:"); lList.listTraverse(); System.out.println("在表头插入0后,此时单链表的长度为:" + lList.getListLength()); System.out.println("第5个元素的值为:" + lList.getElem(5)); System.out.println("第几个结点的值为4:" + lList.locateElem(4)); lList.listTraverse(); System.out.println("此时单链表的长度为:" + lList.getListLength()); lList.listDelete(11); System.out.print("删除第11个元素后:"); lList.listTraverse(); lList.listDelete(5); System.out.print("删除第5个元素后:"); lList.listTraverse(); lList.clearList(); System.out.print("清空单链表后:"); lList.listTraverse(); lList.createListTail(20); System.out.print("使用尾插法整体创建单链表后:"); lList.listTraverse(); lList.clearList(); System.out.print("清空单链表后:"); lList.listTraverse(); lList.createListHead(20); System.out.print("使用头插法整体创建单链表后:"); lList.listTraverse(); } }
- 输出:
初始化后单链表的长度为:0 在表头插入1~5后:5 4 3 2 1 在表头插入1~5后,此时单链表的长度为:5 清空线性表后,此时单链表的长度为:0 在表尾插入1~10后:1 2 3 4 5 6 7 8 9 10 在表头插入1~10后,此时单链表的长度为:10 在表头插入0后:0 1 2 3 4 5 6 7 8 9 10 在表头插入0后,此时单链表的长度为:11 第5个元素的值为:4 第几个结点的值为4:5 0 1 2 3 4 5 6 7 8 9 10 此时单链表的长度为:11 删除第11个元素后:0 1 2 3 4 5 6 7 8 9 删除第5个元素后:0 1 2 3 5 6 7 8 9 清空单链表后: 使用尾插法整体创建单链表后:17 10 42 77 49 96 84 1 1 40 2 21 78 34 65 46 18 60 33 74 清空单链表后: 使用头插法整体创建单链表后:69 81 63 34 58 57 69 61 21 15 0 22 55 87 27 19 75 94 14 85
儿女情长什么的,最影响我们闯荡江湖了。