二、线性表(顺序表,链表)
线性表有两种存储方式:顺序存储和链式存储。采用顺序存储的线性表被称为顺序表,采用链式存储的线性表被称为链表。
线性表是零个元素或者多个数据元素的优先序列。数据元素之间是有顺序的,数据元素的个数是有限的,数据元素的类型必须相同。
顺序存储是表示线性表最简单的方式。 将线性表中的元素一个接着一个的存储在一块连续的存储取悦中,这种顺序表示的线性表也称为顺序表。
顺序表是顺序存储方式,即逻辑上相邻的数据在计算机内的存储位置也是相邻的。在顺序存储方式中,元素存储是连续的,中间不允许有空,可以快速定位某个元素,但是插入、删除时需要移动大量元素。根据分配空间方法的不同,顺序表可以分为静态分配和动态分配两种。
/* 1. 顺序表静态定义需要两个元素: 静态定义就是提前定义好一个数组 1. 定义数组 (最大容量) 2. 元素长度 */ typedef int ElemType; MaxSize = 100; typedef struct SqList { int data[MaxSize]; // ElemType data[MaxSize]; int length; // 元素长度 }SqList;
/* 2. 顺序表的动态定义需要三个元素: 1. 元素必须知道存储的空间首地址 2. 元素的个数 3. 数组容量 */ MaxSize = 100; struct SqList { int *elem; int length; }L;
/* 初始化是指为顺序表分配一段预定义大小的连续空间,elem记录基地址,顺序表长度为0。 */ bool InitList(SqList &L) { L.elem = new int[MaxSize]; if(L.elem == NULL) { cout << "初始化分配失败" << endl; return false; } // 初始化成功 L.length = 0; return true; }
/* 顺序表中的任何一个元素都可以立即找到,称为随机存取方式。要取第i个元素,只要i值合理,可以找到该元素,由于下标是从0开始的,因此第i个元素,其下标为i-1,即对应元素为L.elem[i-1]。 */ if(i < L.length && i >=1) { return L.elem[i-1]; }
/* 在顺序表中查找一个元素e,可以从第一个元素开始顺序查找,依次比较每一个元素值,如果相等,则返回元素位置(位序,即第几个元素),如果查找整个顺序表都没找到,则返回-1。 */ for(int i=0; i<L.length; i++) { if(L.elem[i] == e) { return i+1; } } retrun -1;
/* 在顺序表中第i个位置之前插入一个元素e,需要从最后一个元素开始,后移一位,…,直到把第i个元素也后移一位,然后把e放入第i个位置。 */ if(i < 1 || i > L.length + 1) { return false; } // 最后一个元素开始移动 for(int j=L.length-1; j>=i-1; j--) { L.elem[j+1] = L.elem[j]; } L.elem[i-1] = e;
/* 在顺序表中删除第i个元素,需要把该元素暂存到变量e,然后从i+1个元素开始前移,…,直到把第n个元素也前移一位,即可完成删除操作。 */ if(i >= 1 && i <= L.length) { for(int j=i; j<=L.length-1; j++) { L.elem[j-1] = L.elem[j]; } } L.length--;
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include<string> using namespace std; #define MaxSize 100 // 最大空间 typedef struct { int *elem; // 基地址 int length; // 顺序表的长度 }SqList; // 初始化顺序表 bool InitList(SqList &L) { L.elem = new int[MaxSize]; //为顺序表分配Maxsize个空间 if (L.elem == NULL) { return false; } L.length = 0; //空表长度为0 return true; } // 顺序表创建 bool CreateList(SqList &L) { int count, data; cout << "请输入元素个数: "; cin >> count; for (int i = 0; i < count; i++) { if (L.length == MaxSize) { cout << "顺序表已满" << endl; return false; } cout << "请输入第 " << i + 1 << " 个元素: "; cin >> data; // 输入一个数据元素 L.elem[i] = data; //将数据存入第i个位置 L.length++; // 顺序表长度增1 cout << endl; } return true; } // 顺序表按照位置取值 bool GetElem(SqList &L, int pos, int &ret) { if (pos < 1 || pos > L.length) //判断pos值是否合理,若不合理,返回false { return false; } ret = L.elem[pos - 1]; // 第pos-1的单元存储着第pos个数据 return true; } // 顺序表查找值 int LocateELem(SqList &L, int value, int &ret) { for (int i = 0; i < L.length; i++) { if (L.elem[i] == value) { ret = i + 1; return ret; } } return -1; } // 顺序表插入元素 bool ListInsertData(SqList &L, int pos, int value) { if (pos < 1 || pos > L.length + 1) // pos 值不合法 { return false; } if (L.length == MaxSize) //存储空间已满 { return false; } for (int j = L.length - 1; j >= pos - 1; j--) { L.elem[j + 1] = L.elem[j]; // 从最后一个元素开始后移直到第pos个元素后移 } L.elem[pos - 1] = value; //将新元素 value 放入第 pos 个位置 L.length++; return true; } // 顺序表根据位置删除元素 bool ListDeletePos(SqList &L, int pos, int &ret) { if (pos < 1 || pos > L.length) { return false; } ret = L.elem[pos - 1]; for (int i = pos; i < L.length; i++) { L.elem[i - 1] = L.elem[i]; } L.length--; return true; } // 输出顺序表 void print(SqList &L) { for (int i = 0; i < L.length; i++) { cout << L.elem[i] << " "; } cout << endl; } // 销毁顺序表 void DestortList(SqList &L) { if (L.elem) { delete[] L.elem; } } int main() { SqList L; int pos, value, ret; // 位置,值,返回的值 cout << "1. 初始化" << endl; cout << "2. 创建" << endl; cout << "3. 取值(按位置)" << endl; cout << "4. 查找(某个元素)" << endl; cout << "5. 插入" << endl; cout << "6. 删除" << endl; cout << "7. 输出" << endl; cout << "8. 销毁" << endl; cout << "0. 退出" << endl; int choose = -1; while (choose) { cout << "请选择操作: "; cin >> choose; switch (choose) { case 1: cout << "顺序表初始化" << endl; if (InitList(L)) { cout << "顺序表初始化成功" << endl; } else { cout << "顺序表初始化失败" << endl; } break; case 2: cout << "顺序表创建" << endl; if (CreateList(L)) { cout << "顺序表创建成功!" << endl; } else { cout << "顺序表创建失败!" << endl; } break; case 3: cout << "顺序表按位置取值, 请输入位置: "; cin >> pos; if (GetElem(L, pos, ret)) { cout << "第 " << pos << " 个位置的元素是: " << ret << endl; } else { cout << "顺序表取值失败" << endl; } break; case 4: cout << "请输入顺序表要查找的数: "; cin >> value; if (LocateELem(L, value, ret) == -1) { cout << "查找失败!不存在: " << value << endl; } else { cout << "查找成功!在第 " << ret << " 位置" << endl; } break; case 5: cout << "请输入要插入的位置和要插入的数据元素:"; cin >> pos >> value; if (ListInsertData(L, pos, value)) { cout << "插入成功!" << endl; } else { cout << "插入失败!" << endl; } break; case 6: cout << "请输入要删除的位置: "; cin >> pos; if (ListDeletePos(L, pos, ret)) cout << " 删除成功!删除的元素是" << ret << endl; else cout << "删除失败!" << endl; break; case 7: print(L); break; case 8: DestortList(L); break; default: break; } } return EXIT_SUCCESS; }
三、线性表的链式存储(链表)
typedef struct Lnode { int data; struct Lnode *next; }Lnode, *LinkList;
3.1.2 单链表的创建
3.1.2.1 头插法创建单链表
/* 头插法每次把新结点插入到头结点之后,创建的单链表和数据输入顺序相反。 修改指针的顺序原则:先修改没有指针标记的那一端。 */ S->next = L->next; L->next = S;
/* 尾插法建表每次把新结点链接到链表的尾部,其创建的单链表和数据输入顺序一致。 尾插法一定要有尾指针 r 指向链表最后一个节点,每次插入一个节点,r指针都要向后移动一个节点。 */ S->next = NULL; r->next = S; r = S;
/* 单链表的取值不像顺序表那样可以随机访问任何一个元素,必须从第1个结点开始按顺序向后找,一直找到第i个结点。 */
/* 在一个单链表中查找是否存在元素e,可以定义一个p 指针,指向第一个元素结点,比较p指向结点的数据域是否等于e。 */
/* 如果要在第i个结点之前插入一个元素,则必须先找到第i-1个结点, */
/* 删除一个结点,实际上是把这个结点跳过去。根据单向链表向后操作的特性,要想跳过第i个结点,就必须先找到第i-1个结点。 */
/* 将带有头结点的单链表就地逆置,辅助空间复杂度为O(1)。 设 p指向节点1, q指向节点2, 然后将 p 节点放在 L后面, 然后p指向2, q指向3,用头插法将 p 节点插入到 1 前面。 ...... */
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include<string> using namespace std; typedef struct Lnode { int data; // 数据域 struct Lnode *next; // 指针域 }Lnode, *LinkList; // 单链表初始化 bool InitList(LinkList &L) { L = new Lnode; //生成新结点作为头结点,用头指针L指向头结点 if (L == NULL) { return false; } L->next = NULL; //头结点的指针域置空 return true; } // 头插法创建单链表 void CreatListHead(LinkList &L) { // 初始化头结点, 如果没有初始化头结点,那么如果执行两次此函数操作,链表的长度会增加 L = new Lnode; L->next = NULL; int count; cout << "请输入链表节点合数: "; cin >> count; for (int i = 1; i <= count; i++) { LinkList s = new Lnode; cout << "请输入第 " << i << " 个节点的值: "; cin >> s->data; s->next = L->next; L->next = s; } cout << endl; } // 尾插法创建单链表 void CreatListFeet(LinkList &L) { // 初始化头结点 L = new Lnode; L->next = NULL; int count; LinkList s, r; r = L; // 创建r节点往后移动,因为头结点是不动的。 cout << "请输入节点的个数: "; cin >> count; for (int i = 1; i <= count; i++) { cout << "请输入第 " << i << " 个节点的值: "; s = new Lnode; cin >> s->data; s->next = NULL; r->next = s; //将新结点s插入尾结点*r之后 r = s; //r指向新的尾结点s } cout << endl; } // 单链表取值 bool GetElem(LinkList &L, int pos, int &ret) { LinkList p; int j = 1; p = L->next; while (j<pos && p) //顺链域向后扫描,直到p指向第pos个元素或p为空 { p = p->next; //p指向下一个结点 j++; //计数器j相应加1 } while (j>pos || !p) // pos 值不合法 pos>n或 pos<1 { return false; } ret = p->data; return true; } // 单链表查找元素 bool LocateElem(LinkList &L, int value, int &ret) { LinkList p; p = L->next; int index = 1; while (p) // 顺链域向后扫描,直到p为空或p所指结点的数据域等于value { if (p->data == value) { ret = index; return true; } p = p->next; // p指向下一个节点 index++; } return false; } // 单链表插入值 bool ListInsert(LinkList &L, int pos, int value) { LinkList p = L; LinkList s = new Lnode; int j = 0; while (p && j < pos - 1) // 查找第pos - 1个结点,p指向该结点 { p = p->next; j++; } if (!p || j > pos - 1) // pos > n+1 或者 pos < 1 { return false; } s->data = value; s->next = p->next; p->next = s; return true; } // 单链表删除元素 bool ListDelete(LinkList &L, int pos) { LinkList p; int j = 0; p = L; while ((p->next) && (j < pos - 1)) { p = p->next; j++; } if (!(p->next) || (j > pos - 1)) { return false; } p->next = p->next->next; return true; } // 输出单链表 void ListPrint(LinkList &L) { LinkList p; p = L->next; while (p) { cout << p->data << "\t"; p = p->next; } cout << endl; } int main() { int pos, value, ret; cout << "1. 初始化" << endl; cout << "2. 创建单链表(前插法)" << endl; cout << "3. 创建单链表(尾插法)" << endl; cout << "4. 取值(按位置)" << endl; cout << "5. 查找(某个节点元素)" << endl; cout << "6. 插入" << endl; cout << "7. 删除" << endl; cout << "8. 输出" << endl; cout << "0. 退出" << endl; int choose = -1; LinkList L; while (choose) { cout << "请选择操作: "; cin >> choose; switch (choose) { case 1: cout << "初始化单链表" << endl; if (InitList(L)) { cout << "单链表初始化成功" << endl; } else { cout << "单链表初始化失败" << endl; } break; case 2: cout << "头插法创建单链表" << endl; CreatListHead(L); ListPrint(L); break; case 3: cout << "尾插法创建单链表" << endl; CreatListFeet(L); ListPrint(L); break; case 4: cout << "单链表取值" << endl; cout << "请输入要取值的位置: "; cin >> pos; if (GetElem(L, pos, ret)) { cout << "取值成功,取出来的值是: " << ret << endl; } else { cout << "取值失败, 不存在该位置的值" << endl; } break; case 5: cout << "请输入所要查找元素: "; cin >> value; if (LocateElem(L, value, ret)) { cout << "查找成功, 在第 " << ret << " 个位置" << endl; } else { cout << "查找失败, 不存在: " << value << endl; } break; case 6: cout << "请输入插入的位置和元素(用空格隔开):"; cin >> pos >> value; if (ListInsert(L, pos, value)) { cout << "插入成功.\n\n"; } else { cout << "插入失败!\n\n"; } ListPrint(L); break; case 7: cout << "请输入所要删除的元素位置i: "; cin >> pos; if (ListDelete(L, pos)) { cout << "删除成功!\n"; } else { cout << "删除失败!\n"; } ListPrint(L); break; case 8: ListPrint(L); break; default: break; } } return EXIT_SUCCESS; }
3.2 双向链表
3.2.1 双向链表的定义
/* 单向链表有一个指针,只能向后操作,不可以向前操作。双向链表有两个指针,可以向前后两个方向操作。 */ struct DuLnode { int data; struct DuLnode *next, *prior; }DuLonode, *DuLinkList;
/* 双向链表也可以采用头插法和尾插法创建,一般采用头插法。 */ S->next = L->next; L->next->prior = S; s->prior = L; L->next = S;
/* 单向链表要在第i个结点之前插入一个元素,则必须先找到第i-1个结点。双向链表直接找到第i个结点,就可以把新结点插入到第i个结点之前。 */ p->prior->next = S; S->prior = p->prior; S->next = p; p->prior = S;
/* 删除一个结点,实际上是把这个结点跳过去。单向链表中,必须先找到第i-1个结点,才能把第i个结点跳过去。双向链表直接找到第i个结点,然后修改指针即可。 */ p->prior->next = p->next; p->next->prior-> p->prior;
优点:链表是动态存储,不需要预先分配最大空间。插入删除不需要移动元素。
缺点:每次动态分配一个结点,每个结点的地址是不连续的,需要有指针域记录下一个结点的地址,指针域需要占用一个int的空间,因此存储密度低(数据所占空间/结点所占总空间)。存取元素必须从头到尾按顺序查找,属于顺序存取。
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include<string> using namespace std; typedef struct DuLNode { int data; // 数据域 struct DuLNode *prior, *next; }DuLNode, *DuLinkList; // 双向链表初始化 bool InitDuList(DuLinkList &L) { L = new DuLNode; if (L == NULL) { return false; } L->prior = NULL; L->next = NULL; } // 前插法创建双向链表 void CreateDuList(DuLinkList &L) { int count; DuLinkList s; L = new DuLNode; L->prior = L->next = NULL; cout << "请输入元素个数: "; cin >> count; for (int i = 1; i <= count; i++) { s = new DuLNode; cout << "请输入第 " << i << " 个元素的值: "; cin >> s->data; if (L->next) { L->next->prior = s; } s->next = L->next; // 新节点的下一个节点是原来头结点的下一个节点 s->prior = L; // 新节点的前驱结点是头结点 L->next = s; //将新结点s插入到头结点之后 } } // 双向链表取值 bool GetElem(DuLinkList &L, int pos, int &ret) { DuLinkList p; p = L->next; int j = 1; while (j < pos && p) { p = p->next; j++; } if (!p || j > pos) { return false; } ret = p->data; return true; } // 双向链表查找值 bool LocateElem(DuLinkList &L, int value, int &ret) { DuLinkList p; p = L->next; int j = 1; while (p) { if (p->data == value) { ret = j; return true; } j++; p = p->next; } return false; } // 双向链表插入值 bool ListInsert(DuLinkList &L, int pos, int value) { DuLinkList p = L; DuLinkList s = new DuLNode; int j = 0; while (p && j < pos) { p = p->next; j++; } if (!p || j > pos) { return false; } s->data = value; p->prior->next = s; s->prior = p->prior; s->next = p; p->prior = s; return true; } // 双线链表删除 bool ListDelete(DuLinkList &L, int pos, int &ret) { DuLinkList p = L; int j = 0; while (p && j < pos) { p = p->next; j++; } if (!p || j > pos) { return false; } if (p->next) // 如果 p 的直接后继节点存在的话 { p->next->prior = p->prior; } p->prior->next = p->next; ret = p->data; delete p; return true; } // 输出双向链表 void Listprint(DuLinkList &L) { DuLinkList p; p = L->next; while (p) { cout << p->data << "\t"; p = p->next; } cout << endl; } int main() { int pos, value, ret; int choose = -1; DuLinkList L; while (choose) { cout << "1. 初始化\n"; cout << "2. 创建双向链表(头插法)\n"; cout << "3. 取值\n"; cout << "4. 查找\n"; cout << "5. 插入\n"; cout << "6. 删除\n"; cout << "7. 输出\n"; cout << "0. 退出\n"; cout << "请输入数字选择:"; cin >> choose; switch (choose) { case 1: cout << "双向链表初始化" << endl; if (InitDuList(L)) cout << "初始化一个空的双向链表!\n"; else { cout << "初始化失败" << endl; } break; case 2: cout << "前插法创建双项链表" << endl; CreateDuList(L); cout << "前插法创建双向链表输出结果:\n"; Listprint(L); break; case 3: cout << "请输入一个位置用来取值: "; cin >> pos; if (GetElem(L, pos, ret)) { cout << "查找成功" << endl; cout << "第 " << pos << " 个元素是:" << ret << endl; } else { cout << "查找失败,不存在位置: " << pos << endl; } break; case 4: cout << "请输入所要查找元素: "; cin >> value; if (LocateElem(L, value, ret)) { cout << "查找成功, 在第 " << ret << " 个位置" << endl; } else { cout << "查找失败,不存在这个元素 " << endl; } break; case 5: cout << "双向链表位置之前插入值,请输入插入的位置和元素(用空格隔开): "; cin >> pos >> value; if (ListInsert(L, pos, value)) { cout << "插入成功!" << endl; } else { cout << "插入失败!" << endl; } Listprint(L); break; case 6: cout << "请输入所要删除的元素位置: "; cin >> pos; if (ListDelete(L, pos, ret)) { cout << "删除成功, 删除的元素是: " << ret << endl; } else { cout << "删除失败, 不存在该位置" << endl; } Listprint(L); break; case 7: Listprint(L); break; default: break; } } return EXIT_SUCCESS; }
带有头结点的单链表L,设计一个高效算法求L中的中间结点。
解题思路:使用快慢指针来解决。一个快指针,一个慢指针,快指针走两步慢指针走一步,当快指针指向结尾的时候,慢指针刚好指向中间结点。
LinkList findmiddle(LinkList L) { LinkList p, q; p = L; // p为快指针 q = L; // q为慢指针 while(p!=NULL && p->next!=NULL) { p = p->next->next; // 快指针走两步 q = q->next; // 慢指针走一步 } return q; // 返回中间节点的指针 }
思考:如何在单链表中查找倒数第k个结点?
仍然可以使用快慢指针,慢指针不要动,快指针先走k-1步,然后两个指针一起同样的速度走,当快指针走到终点时,慢指针正好停留在倒数第k个结点。为什么呢?
因为它们之间的距离始终保持k-1。