一 (单)链表 ADT
+ Status InitList(LinkList &L) 初始化(单)链表
+ void printList(LinkList L) 遍历(单)链表
+ int ListLength(LinkList L) 获得表长
+ void CreateList_Head(LinkList &L, int n) 创建单链表 (头插法)【重点】
+ void CreateList_Rear(LinkList &L, int n) 创建单链表 (尾插法) 【重点】
+ Status GetElement(LinkList L, int i, ElementType &e) (按位)取值
+ LNode *LocateElement(LinkList L, ElementType e) (按值)查找
+ Status ListInsert(LinkList &L, int i, ElementType e) (按位)插入 【重点 | 易错】
+ Status ListDelete(LinkList &L, int i) (按位)删除 【重点 | 易错/易混】
二 编程实现
2.1 定义基础数据类型
ElementType (数据元素类型/结构体)
struct ElementType { char data; // char -> ElementType bool operator==(const ElementType b) const{ // 重载结构体 ElementType 的运算符 return this->data == b.data; } bool operator!=(const ElementType b) const{ return this->data != b.data; } };
Status (状态/枚举类型)
enum Status { ERROR, OK, OVERFLOW };
LNode(单链表结点/结构体) / LinkList (单链表/结构体)
typedef struct LNode { // typedef 关键字 可使用它来为类型取一个新的名称 like: typedef unsigned char BYTE; ElementType data; //数据域 LNode *next; // 指针域 }*LinkList; // *LinkList 本质上等同于 *LNode
2.2 初始化(单)链表
Status InitList(LinkList &L)
Status InitList(LinkList &L){ L = new LNode; // 生成新结点 作为 头结点 , 头指针L指向头结点 L->next = NULL; // next指针 置空(挂起) return OK; }
2.3 遍历(单)链表
void printList(LinkList L)
void printList(LinkList L){ LNode *p; p = L->next; printf("[LinkList.h#printList] List->elements: "); while(p != NULL){ printElementType(p->data); printf("\t"); p = p->next; } printf("\n"); }
2.4 获得表长
int ListLength(LinkList L)
int ListLength(LinkList L){ LNode *p = L->next; // 指针p 指向首元结点 int i = 0; // while(p){ // 当前结点不为空 p = p->next; i ++; // 长度 +1 } return i; }
2.5-1 创建单链表 (头插法)
void CreateList_Head(LinkList &L, int n)
void CreateList_Head(LinkList &L, int n){ // n: 要输入的元素个数 L = new LNode; // 生成头结点 L->next = NULL; for(int i=0;i<n;i++){ LNode *s = new LNode; // 生成新结点 printf("\ninput -> element: "); cin>>( s->data.data ); s->next = L->next; //链接 新结点 L->next = s; } }
2.5-2 创建单链表 (尾插法)
void CreateList_Rear(LinkList &L, int n)
void CreateList_Rear(LinkList &L, int n){ // n: 要输入的元素个数 L = new LNode; L->next = NULL; LNode *rear = L; // 初始化 尾指针 rear 指向 头结点 for(int j=0; j<n; j++){ LNode *s = new LNode; // 生成新结点 printf("\ninput -> element: "); cin>>s->data.data; s->next = rear->next; //链接 新结点 rear->next = s; rear = rear->next; // 尾指针 rear 指向 新生成的尾结点 } }
2.6 (按位)取值
Status GetElement(LinkList L, int i, ElementType &e)
Status GetElement(LinkList L, int i, ElementType &e){ LNode *p = L->next; // 指针p 指向首元结点 int j = 1; while(p != NULL && j<i){ p = p->next; j++; } if(j>i || p==NULL){ // i值不合法 i>n 或 i<0 return ERROR; } e = p->data; // 赋值 return OK; }
2.7 (按值)查找
LNode *LocateElement(LinkList L, ElementType e)
LNode *LocateElement(LinkList L, ElementType e){ LNode *p = L->next; // 指针p 指向首元结点 while(p!=NULL && p->data!=e){ p = p->next; } return NULL; }
2.8 (按位)插入
Status ListInsert(LinkList &L, int i, ElementType e)
Status ListInsert(LinkList &L, int i, ElementType e){ LNode *p = L; // 指针p 指向头结点 【易错: 必须是头结点, 如果初始化p指向 首元结点(p = L->next), 那么 当i=1时, 会与 if(j>i-1 || p == NULL)中的"p == NULL"自相矛盾】 int j = 0; // j 表示下标(明确j的意义很重要, 到底是 表示 下标(从0开始) 还是位置序号(从1开始) ) while(j<i-1 && p!= NULL){ // 移动指针p 指向 第 i-1 个元素上 (即 使得 下标 j 最大等于 i-2) p = p->next; j++; } if(j>i-1 || p == NULL){ // 判断 i 值 是否合法 i>n 或者 i<0 return ERROR; } LNode *s = new LNode; // 生成新结点 s->data = e; s->next = p->next; // 链接 新结点 与 原第 i 个结点 p->next = s; return OK; }
2.9 (按位)删除
Status ListDelete(LinkList &L, int i)
Status ListDelete(LinkList &L, int i){ LNode *p = L; //指针p 指向首元结点 int j = 0; // j 表示 下标 (j 用于 下一次的循环判断条件) LNode *q; // 定位被删除结点 while( (p != NULL) && (j<i-1) ){ // 指针p 移动到 第 i-1 个元素上 (即 j 最大取值为: i-1 ) //【易混】 (p != NULL) 可以换成 (p->next != NULL),无任何影响,原因:设置该判断条件主要目的是 为了 指针p 能够继续往后移动 printf("(old) p->data: %c\n", p->data.data); p = p->next; printf("(new) p->data: %c\n--------------\n", p->data.data); j++; } if( j > i-1 || p->next == NULL ){ // 【易错: p != NULL (考虑的因素是: i>n )】 //【易错】 此处的 p->next == NULL 的判断条件不能轻易换成 p == NULL 因为 必须满足 p 指向于 被删除的第 i 个 (非空)结点的前置结点 // 即 要满足 第 i 个结点非空 ,且 p 指向的是其前置结点 所以 只能为 p->next == NULL printf("j:%d\n", j); return ERROR; } q = p->next; p->next = q->next; delete q; // 释放被删除结点的空间 return OK; }
三 测试运行(Main.cpp)
#include <stdio.h> //#include <iostream.h> #include <iostream> using namespace std; #include "base.h" #include "LinkList.h" int main(){ LinkList L; // *LinkList 本质上等同于 *LNode InitList(L); // 初始化(单)链表 L int i = ListLength(L); printf("length: %d \n",i); ElementType e; Status status; e.data = 'A'; status = ListInsert(L, 1, e); printf("status (after insert) : %d\n", status); printList(L); e.data = 'B'; status = ListInsert(L, 2, e); printf("status (after insert) : %d\n", status); printList(L); e.data = 'C'; status = ListInsert(L, 3, e); printf("status (after insert) : %d\n", status); printList(L); status = ListDelete(L, 0); printf("status (after delete) : %d\n", status); printList(L); status = ListDelete(L, 4); printf("status (after delete) : %d\n", status); printList(L); status = ListDelete(L, 3); printf("status (after delete) : %d\n", status); printList(L); CreateList_Rear(L, 5); // input: A B C D E printList(L); return 0; }
运行结果
length: 0 status (after insert) : 1 [LinkList.h#printList] List->elements: A status (after insert) : 1 [LinkList.h#printList] List->elements: A B status (after insert) : 1 [LinkList.h#printList] List->elements: A B C j:0 status (after delete) : 0 [LinkList.h#printList] List->elements: A B C (old) p->data: (new) p->data: A -------------- (old) p->data: A (new) p->data: B -------------- (old) p->data: B (new) p->data: C -------------- j:3 status (after delete) : 0 [LinkList.h#printList] List->elements: A B C (old) p->data: (new) p->data: A -------------- (old) p->data: A (new) p->data: B -------------- status (after delete) : 1 [LinkList.h#printList] List->elements: A B input -> element: A input -> element: B input -> element: C input -> element: D input -> element: E [LinkList.h#printList] List->elements: A B C D E
四 参考资料
1 《数据结构(C语言版 第二版)》.严蔚敏.李冬梅.吴伟民