1. 引言
本文主要讲解一个线性表 (应用举例)
2. 具有实际意义的线性链表
#include "ds.h" using namespace std; typedef int ElemType; // 结点类型 typedef struct LNode{ ElemType data; struct LNode *next; }*Link,*Position; typedef struct LinkList{ Link head, tail; // 分别指向线性链表中的头结点和最后一个结点 int len; // 指示线性链表中数据元素的个数 }LinkList; void MakeNode(Link &p, ElemType e); void FreeNode(Link &p); void InitList(LinkList &L); void ClearList(LinkList &L); void DestroyList(LinkList &L); // h指向L的一个结点,把h当做头结点,将s所指结点插入在第一个结点之前, 形参增加L,因为需修改L void InsFirst(LinkList &L,Link h,Link s); // h指向L的一个结点,把h当做头结点,删除链表中的第一个结点并以q返回。 // 若链表为空(h指向尾结点),q=NULL,返回FALSE Status DelFirst(LinkList &L, Link h, Link &q); // 将指针s(s->data为第一个数据元素)所指(彼此以指针相链,以NULL结尾)的 // 一串结点链接在线性链表L的最后一个结点之后,并改变链表L的尾指针指向新的尾结点 void Append(LinkList &L, Link s); // 已知p指向线性链表L中的一个结点,返回p所指结点的直接前驱的位置。若无前驱,则返回NULL Position PriorPos(LinkList L, Link p); // 删除线性链表L中的尾结点并以q返回,改变链表L的尾指针指向新的尾结点 Status Remove(LinkList &L, Link &q); // 已知p指向线性链表L中的一个结点,将s所指结点插入在p所指结点之前, // 并修改指针p指向新插入的结点 void InsBefore(LinkList &L, Link &p, Link s); // 已知p指向线性链表L中的一个结点,将s所指结点插入在p所指结点之后, // 并修改指针p指向新插入的结点 void InsAfter(LinkList &L,Link &p,Link s); // 已知p指向线性链表中的一个结点,用e更新p所指结点中数据元素的值 void SetCurElem(Link p, ElemType e); ElemType GetCurElem(Link p); Status ListEmpty(LinkList L); int ListLength(LinkList L); Position GetHead(LinkList L); Position GetLast(LinkList L); Position NextPos(Link p); // 返回p指示线性链表L中第i个结点的位置,并返回OK,i值不合法时返回ERROR。i=0为头结点 Status LocatePos(LinkList L, int i, Link &p); // 返回线性链表L中第1个与e满足函数compare()判定关系的元素的位置, // 若不存在这样的元素,则返回NULL Position LocateElem(LinkList L, ElemType e, Status(*compare)(ElemType, ElemType)); void ListTraverse(LinkList L, void(*visit)(ElemType) ); // 已知L为有序线性链表,将元素e按非降序插入在L中。 void OrderInsert(LinkList &L, ElemType e, int (*com)(ElemType, ElemType)); // 若升序链表L中存在与e满足判定函数compare()取值为0的元素,则q指示L中 // 第一个值为e的结点的位置,并返回TRUE;否则q指示第一个与e满足判定函数 // compare()取值>0的元素的前驱的位置。并返回FALSE。(用于一元多项式) Status LocateElem(LinkList L,ElemType e,Position &q,int(*compare)(ElemType,ElemType)); // 分配由p指向的值为e的结点。若分配失败,则退出 void MakeNode(Link &p, ElemType e) { p = (Link)malloc(sizeof(LNode)); if (!p) exit(ERROR); memcpy(&(p->data), &e, sizeof(ElemType)); } // 释放p所指结点 void FreeNode(Link &p) { free(p); p = NULL; } // 构造一个空的线性链表L void InitList(LinkList &L) { Link p; p = (Link)malloc(sizeof(LNode)); // 生成头结点 if (p) { p->next = NULL; L.head = L.tail = p; L.len = 0; } else exit(ERROR); } // 将线性链表L重置为空表,并释放原链表的结点空间 void ClearList(LinkList &L) { Link p, q; if (L.head != L.tail) { p = q = L.head->next; L.head->next = NULL; while (p != L.tail) { q = p->next; free(p); p = q; } free(q); // 释放尾节点 L.tail = L.head; L.len = 0; } } // 销毁线性链表L,L不再存在 void DestroyList(LinkList &L) { ClearList(L); FreeNode(L.head); L.tail = NULL; L.len = 0; } // h指向L的一个结点,把h当做头结点,将s所指结点插入在第一个结点之前, 形参增加L,因为需修改L void InsFirst(LinkList &L,Link h,Link s) { s->next = h->next; h->next = s; if (h == L.tail) L.tail = s; L.len++; } // h指向L的一个结点,把h当做头结点,删除链表中的第一个结点并以q返回。 // 若链表为空(h指向尾结点),q=NULL,返回FALSE Status DelFirst(LinkList &L, Link h, Link &q) { q = h->next; if (q) // 链表非空 { h->next = q->next; if(!h->next) // 删除尾结点 L.tail = h; // 修改尾指针 L.len--; return OK; } else return FALSE; // 链表空 } // 将指针s(s->data为第一个数据元素)所指(彼此以指针相链,以NULL结尾)的 // 一串结点链接在线性链表L的最后一个结点之后,并改变链表L的尾指针指向新的尾结点 void Append(LinkList &L, Link s) { int i = 1; Link p = s; if (NULL == p) return; L.tail->next = s; while (p->next) { i++; p = p->next; } L.tail = p; L.len += i; } // 已知p指向线性链表L中的一个结点,返回p所指结点的直接前驱的位置。若无前驱,则返回NULL Position PriorPos(LinkList L, Link p) { Link s = L.head->next; if (p == NULL || p == L.head || p == s) return NULL; while (s->next) { if (p == s->next) return s; s = s->next; } } // 删除线性链表L中的尾结点并以q返回,改变链表L的尾指针指向新的尾结点 Status Remove(LinkList &L, Link &q) { Link p=L.head; if(L.len==0) // 空表 { q=NULL; return FALSE; } while(p->next!=L.tail) p=p->next; q=L.tail; p->next=NULL; L.tail=p; L.len--; return OK; } // 已知p指向线性链表L中的一个结点,将s所指结点插入在p所指结点之前, // 并修改指针p指向新插入的结点 void InsBefore(LinkList &L, Link &p, Link s) { Link temp = L.head; while (temp->next != p) { temp = temp->next; } s->next = temp->next; temp->next = s; p = s; L.len++; } // 已知p指向线性链表L中的一个结点,将s所指结点插入在p所指结点之后, // 并修改指针p指向新插入的结点 void InsAfter(LinkList &L,Link &p,Link s) { if (p == L.tail) { p->next = s; s->next = NULL; L.tail = s; } else { s->next = p->next; p->next = s; } p = s; L.len++; } // 已知p指向线性链表中的一个结点,用e更新p所指结点中数据元素的值 void SetCurElem(Link p, ElemType e) { memcpy(&(p->data), &e, sizeof(sizeof(ElemType))); } ElemType GetCurElem(Link p) { return p->data; } Status ListEmpty(LinkList L) { if (0 == L.len) return TRUE; else return FALSE; } int ListLength(LinkList L) { return L.len; } Position GetHead(LinkList L) { return L.head; } Position GetLast(LinkList L) { return L.tail; } Position NextPos(Link p) { // 已知p指向线性链表L中的一个结点,返回p所指结点的直接后继的位置。若无后继,则返回NULL return p->next; } // 返回p指示线性链表L中第i个结点的位置,并返回OK,i值不合法时返回ERROR。i=0为头结点 Status LocatePos(LinkList L, int i, Link &p) { int j = 0; p = L.head; while (j < i && p != NULL) { j++; p = p->next; } if (j > i || !p ) return ERROR; return OK; } // 返回线性链表L中第1个与e满足函数compare()判定关系的元素的位置, // 若不存在这样的元素,则返回NULL Position LocateElem(LinkList L, ElemType e, Status(*compare)(ElemType, ElemType)) { Link p = L.head->next; while (p && !compare(e, p->data)) p = p->next; return p; } void ListTraverse(LinkList L, void(*visit)(ElemType) ) { Link p = L.head->next; while (p) { visit(p->data); p = p->next; } printf("\n"); } // 已知L为有序线性链表,将元素e按非降序插入在L中。 void OrderInsert(LinkList &L, ElemType e, int (*com)(ElemType, ElemType)) { Link o,p,q; q=L.head; p=q->next; while(p!=NULL&&com(p->data,e)<0) // p不是表尾且元素值小于e { q=p; p=p->next; } o=(Link)malloc(sizeof(LNode)); // 生成结点 o->data=e; // 赋值 q->next=o; // 插入 o->next=p; L.len++; // 表长加1 if(!p) // 插在表尾 L.tail=o; // 修改尾结点 } // 若升序链表L中存在与e满足判定函数compare()取值为0的元素,则q指示L中 // 第一个值为e的结点的位置,并返回TRUE;否则q指示第一个与e满足判定函数 // compare()取值>0的元素的前驱的位置。并返回FALSE。(用于一元多项式) Status LocateElem(LinkList L,ElemType e,Position &q,int(*compare)(ElemType,ElemType)) { Link p=L.head,pp; do { pp=p; p=p->next; }while(p&&(compare(p->data,e)<0)); // 没到表尾且p->data.expn<e.expn if(!p||compare(p->data,e)>0) // 到表尾或compare(p->data,e)>0 { q=pp; return FALSE; } else // 找到 { q=p; return TRUE; } } Status equal(ElemType c1,ElemType c2) { // 判断是否相等的函数 if(c1==c2) return TRUE; else return FALSE; } int comp(ElemType a,ElemType b) { // 根据a<、=或>b,分别返回-1、0或1 if(a==b) return 0; else return (a-b)/abs(a-b); } void print(ElemType e) { printf("%d ", e); } int main() { Link p,h; LinkList L; Status i; int j,k; InitList(L); // 初始化空的线性表L for(j=1;j<=2;j++) { MakeNode(p,j); // 生成由p指向、值为j的结点 InsFirst(L,L.tail,p); // 插在表尾 } OrderInsert(L,0,comp); // 按升序插在有序表头 for(j=0;j<=3;j++) { i=LocateElem(L,j,p,comp); if(i) printf("链表中有值为%d的元素。\n",p->data); else printf("链表中没有值为%d的元素。\n",j); } printf("输出链表:"); ListTraverse(L,print); // 输出L for(j=1;j<=4;j++) { printf("删除表头结点:"); DelFirst(L,L.head,p); // 删除L的首结点,并以p返回 if(p) printf("%d\n",GetCurElem(p)); else printf("表空,无法删除 p=%u\n",p); } printf("L中结点个数=%d L是否空 %d(1:空 0:否)\n",ListLength(L),ListEmpty(L)); MakeNode(p,10); p->next=NULL; // 尾结点 for(j=4;j>=1;j--) { MakeNode(h,j*2); h->next=p; p=h; } // h指向一串5个结点,其值依次是2 4 6 8 10 Append(L,h); // 把结点h链接在线性链表L的最后一个结点之后 OrderInsert(L,12,comp); // 按升序插在有序表尾头 OrderInsert(L,7,comp); // 按升序插在有序表中间 printf("输出链表:"); ListTraverse(L,print); // 输出L for(j=1;j<=2;j++) { p=LocateElem(L,j*5,equal); if(p) printf("L中存在值为%d的结点。\n",j*5); else printf("L中不存在值为%d的结点。\n",j*5); } for(j=1;j<=2;j++) { LocatePos(L,j,p); // p指向L的第j个结点 h=PriorPos(L,p); // h指向p的前驱 if(h) printf("%d的前驱是%d。\n",p->data,h->data); else printf("%d没前驱。\n",p->data); } k=ListLength(L); for(j=k-1;j<=k;j++) { LocatePos(L,j,p); // p指向L的第j个结点 h=NextPos(p); // h指向p的后继 if(h) printf("%d的后继是%d。\n",p->data,h->data); else printf("%d没后继。\n",p->data); } printf("L中结点个数=%d L是否空 %d(1:空 0:否)\n",ListLength(L),ListEmpty(L)); p=GetLast(L); // p指向最后一个结点 SetCurElem(p,15); // 将最后一个结点的值变为15 printf("第1个元素为%d 最后1个元素为%d\n",GetCurElem(GetHead(L)->next),GetCurElem(p)); MakeNode(h,10); InsBefore(L,p,h); // 将10插到尾结点之前,p指向新结点 p=p->next; // p恢复为尾结点 MakeNode(h,20); InsAfter(L,p,h); // 将20插到尾结点之后 k=ListLength(L); printf("依次删除表尾结点并输出其值:"); for(j=0;j<=k;j++) if(!(i=Remove(L,p))) // 删除不成功 printf("删除不成功 p=%u\n",p); else printf("%d ",p->data); MakeNode(p,29); // 重建具有1个结点(29)的链表 InsFirst(L,L.head,p); DestroyList(L); // 销毁线性链表L printf("销毁线性链表L之后: L.head=%u L.tail=%u L.len=%d\n",L.head,L.tail,L.len); }
3. 利用无头结点的单链表处理学生健康登记表
// 利用无头结点的单链表处理学生健康登记表 #include "ds.h" #define NAMELEN 40 #define CLASSLEN 20 struct stud { char name[NAMELEN+1]; long num; char sex; int age; char Class[CLASSLEN+1]; int health; }; typedef stud ElemType; typedef struct LNode{ ElemType data; struct LNode *next; }LNode, *LinkList; //#define DestroyList ClearList void InitList(LinkList &L); void ClearList(LinkList &L); Status ListEmpty(LinkList L); int ListLength(LinkList L); Status GetElem(LinkList L, int i, ElemType &e); int LocateElem(LinkList L,ElemType e,Status(*compare)(ElemType,ElemType)); Status PriorElem(LinkList L, ElemType cur_e, ElemType &pre_e); Status NextElem(LinkList L, ElemType cur_e, ElemType &next_e); Status ListInsert(LinkList &L, int i, ElemType e); Status ListDelete(LinkList &L, int i, ElemType &e); void ListTraverse(LinkList L, void (*vi)(ElemType)); void InsertAscend(LinkList &L,ElemType e,int(*compare)(ElemType,ElemType)); LinkList Point(LinkList L,ElemType e,Status(*equal)(ElemType,ElemType),LinkList &p); Status DeleteElem(LinkList &L,ElemType &e,Status(*equal)(ElemType,ElemType)); void Print(stud e); Status compare(ElemType a, ElemType b); void InitList(LinkList &L) { L = NULL; } void ClearList(LinkList &L) { LinkList p = L, q; while (p) { q = p->next; free(p); p = q; } L = NULL; } Status ListEmpty(LinkList L) { if (L == NULL) return TRUE; else return FALSE; } int ListLength(LinkList L) { int i = 0; LinkList p = L; while (p) { p = p->next; i++; } return i; } Status GetElem(LinkList L, int i, ElemType &e) { int j = 1; LinkList p = L; while (p && j < i) { p = p->next; } if (!p || j > i) return ERROR; memcpy(&e, &(p->data), sizeof(ElemType)); return OK; } int LocateElem(LinkList L, ElemType e, Status (*compare)(ElemType, ElemType)) { LinkList p = L; int j = 0; while (p) { j++; if (compare(e, p->data)) return j; p = p->next; } return 0; } #ifdef PRIOR_NEXT Status PriorElem(LinkList L, ElemType cur_e, ElemType &pre_e) { LinkList p = L, q; while (p->next) { q = p->next; if (cur_e == q->data) { pre_e = p->data; return OK; } p = q; } return ERROR; } Status NextElem(LinkList L, ElemType cur_e, ElemType &next_e) { LinkList p = L; while (p->next) { if (cur_e == p->data) { next_e = p->next->data; return OK; } p = p->next; } return ERROR; } #endif Status ListInsert(LinkList &L, int i, ElemType e) { LinkList p = L, s; int j = 1; s = (LinkList)malloc(sizeof(LNode)); memcpy(&(s->data), &e, sizeof(ElemType)); if (1 == i) { s->next = L; L = s; } else { while (p && j < i - 1) { j++; p = p->next; } if (!p || j > i-1) return ERROR; s->next = p->next; p->next = s; } return OK; } Status ListDelete(LinkList &L, int i, ElemType &e) { LinkList p = L, q; int j = 1; if (1 == i) { q = L; L = p->next; } else { while (p->next && j < i - 1) { j++; p = p->next; } if (!p->next || j > i-1) return ERROR; q = p->next; p->next = q->next; } memcpy(&e, &(q->data), sizeof(ElemType)); free(q); q = NULL; return OK; } void ListTraverse(LinkList L, void (*vi)(ElemType)) { LinkList p = L; while (p) { vi(p->data); p = p->next; } printf("\n"); } void InsertAscend(LinkList &L,ElemType e,int(*compare)(ElemType,ElemType)) { // 按关键字非降序将e插入表L。函数compare()返回值为:形参1的关键字-形参2的关键字 LinkList q=L; if(!L||compare(e,L->data)<=0) // 链表空或e的关键字小于等于首结点的关键字 ListInsert(L,1,e); // 将e插在表头,在bo2-8.cpp中 else { while(q->next&&compare(q->next->data,e)<0) // q不是尾结点且q的下一结点关键字<e的关键字 q=q->next; ListInsert(q,2,e); // 插在q所指结点后(将q作为头指针) } } LinkList Point(LinkList L,ElemType e,Status(*equal)(ElemType,ElemType),LinkList &p) { // 查找表L中满足条件的结点。如找到,返回指向该结点的指针,p指向该结点的前驱(若该结点是 // 首元结点,则p=NULL)。如表L中无满足条件的结点,则返回NULL,p无定义。 // 函数equal()的两形参的关键字相等,返回OK;否则返回ERROR int i,j; i=LocateElem(L,e,equal); if(i) // 找到 { if(i==1) // 是首元结点 { p=NULL; return L; } p=L; for(j=2;j<i;j++) p=p->next; return p->next; } return NULL; // 没找到 } Status DeleteElem(LinkList &L,ElemType &e,Status(*equal)(ElemType,ElemType)) { // 删除表L中满足条件的结点,并返回TRUE;如无此结点,则返回FALSE。 // 函数equal()的两形参的关键字相等,返回OK;否则返回ERROR LinkList p,q; q=Point(L,e,equal,p); if(q) // 找到此结点,且q指向该结点 { if(p) // 该结点不是首元结点,p指向其前驱 ListDelete(p,2,e); // 将p作为头指针,删除第2个结点 else // 该结点是首元结点 ListDelete(L,1,e); return TRUE; } return FALSE; } char sta[3][9]={"health","General","psyhical"}; // 健康状况(3类) FILE *fp; // 全局变量 void Print(stud e) { // 显示记录e的内容 printf("%-8s %6ld",e.name,e.num); if(e.sex=='m') printf(" 男"); else printf(" 女"); printf("%5d %-4s",e.age,e.Class); printf("%9s\n",sta[e.health]); } void ReadIn(stud &e) { // 由键盘输入结点信息 printf("请输入姓名(<=%d个字符): ",NAMELEN); scanf("%s",e.name); printf("请输入学号: "); scanf("%ld",&e.num); printf("请输入性别(m:男 f:女): "); scanf("%*c%c",&e.sex); printf("请输入年龄: "); scanf("%d",&e.age); printf("请输入班级(<=%d个字符): ",CLASSLEN); scanf("%s",e.Class); printf("请输入健康状况(0:%s 1:%s 2:%s):",sta[0],sta[1],sta[2]); scanf("%d",&e.health); } void WriteToFile(stud e) { // 将结点信息写入fp指定的文件 fwrite(&e,sizeof(stud),1,fp); } Status ReadFromFile(stud &e) { // 由fp指定的文件读取结点信息到e int i; i=fread(&e,sizeof(stud),1,fp); if(i==1) // 读取文件成功 return OK; else return ERROR; } int cmp(ElemType c1,ElemType c2) { return (int)(c1.num-c2.num); } void Modify(LinkList &L,ElemType e) { // 修改结点内容,并按学号将结点非降序插入链表L char s[80]; Print(e); // 显示原内容 printf("请输入待修改项的内容,不修改的项按回车键保持原值:\n"); printf("请输入姓名(<=%d个字符): ",NAMELEN); gets(s); if(strlen(s)) strcpy(e.name,s); printf("请输入学号: "); gets(s); if(strlen(s)) e.num=atol(s); printf("请输入性别(m:男 f:女): "); gets(s); if(strlen(s)) e.sex=s[0]; printf("请输入年龄: "); gets(s); if(strlen(s)) e.age=atoi(s); printf("请输入班级(<=%d个字符): ",CLASSLEN); gets(s); if(strlen(s)) strcpy(e.Class,s); printf("请输入健康状况(0:%s 1:%s 2:%s):",sta[0],sta[1],sta[2]); gets(s); if(strlen(s)) e.health=atoi(s); // 修改完毕 InsertAscend(L,e,cmp); // 把q所指结点的内容按学号非降序插入L } #define N 4 // student记录的个数 Status EqualNum(ElemType c1,ElemType c2) { if(c1.num==c2.num) return OK; else return ERROR; } Status EqualName(ElemType c1,ElemType c2) { if(strcmp(c1.name,c2.name)) return ERROR; else return OK; } int main() { // 表的初始记录 stud student[N] = { {"wangxiaoling",790631,'m',18,"CLS91",0}, {"chenhong",790632,'f',20,"CLS91",1}, {"ling",790633,'m',21,"CLS91",0}, {"zhanglili",790634,'m',17,"CLS91",2}}; int i,j,flag=1; char filename[13]; ElemType e; LinkList T,p,q; InitList(T); // 初始化链表 while(flag) { printf("1:将结构体数组student中的记录按学号非降序插入链表\n"); printf("2:将文件中的记录按学号非降序插入链表\n"); printf("3:键盘输入新记录,并将其按学号非降序插入链表\n"); printf("4:删除链表中第一个有给定学号的记录\n"); printf("5:删除链表中第一个有给定姓名的记录\n"); printf("6:修改链表中第一个有给定学号的记录\n"); printf("7:修改链表中第一个有给定姓名的记录\n"); printf("8:查找链表中第一个有给定学号的记录\n"); printf("9:查找链表中第一个有给定姓名的记录\n"); printf("10:显示所有记录 11:将链表中的所有记录存入文件 12:结束\n"); printf("请选择操作命令: "); scanf("%d",&i); switch(i) { case 1: for(j=0;j<N;j++) InsertAscend(T,student[j],cmp); break; case 2: printf("请输入文件名: "); scanf("%s",filename); if((fp=fopen(filename,"rb"))==NULL) printf("打开文件失败!\n"); else { while(ReadFromFile(e)) InsertAscend(T,e,cmp); fclose(fp); } break; case 3: ReadIn(e); InsertAscend(T,e,cmp); break; case 4: printf("请输入待删除记录的学号: "); scanf("%ld",&e.num); if(!DeleteElem(T,e,EqualNum)) printf("没有学号为%ld的记录\n",e.num); break; case 5: printf("请输入待删除记录的姓名: "); scanf("%*c%s",e.name); // %*c吃掉回车符 if(!DeleteElem(T,e,EqualName)) printf("没有姓名为%s的记录\n",e.name); break; case 6: printf("请输入待修改记录的学号: "); scanf("%ld%*c",&e.num); if(!DeleteElem(T,e,EqualNum)) // 在链表中删除该结点,并由e返回 printf("没有学号为%ld的记录\n",e.num); else Modify(T,e); // 修改e并按学号插入链表T break; case 7: printf("请输入待修改记录的姓名: "); scanf("%*c%s%*c",e.name); // %*c吃掉回车符 if(!DeleteElem(T,e,EqualName)) printf("没有姓名为%s的记录\n",e.name); else Modify(T,e); break; case 8: printf("请输入待查找记录的学号: "); scanf("%ld",&e.num); if(!(q=Point(T,e,EqualNum,p))) printf("没有学号为%ld的记录\n",e.num); else Print(q->data); break; case 9: printf("请输入待查找记录的姓名: "); scanf("%*c%s",e.name); if(!(q=Point(T,e,EqualName,p))) printf("没有姓名为%s的记录\n",e.name); else Print(q->data); break; case 10:printf(" 姓名 学号 性别 年龄 班级 健康状况\n"); ListTraverse(T,Print); break; case 11:printf("请输入文件名: "); scanf("%s",filename); if((fp=fopen(filename,"wb"))==NULL) printf("打开文件失败!\n"); else ListTraverse(T,WriteToFile); fclose(fp); break; case 12:flag=0; } } return 0; }