一、基本概念:
1.线性表:是N个数据元素的有限序列。
2.特点:
1)存在唯一一个被称为“第一个”的元素;
2)存在唯一一个被称为“最后一个”的元素;
3)除第一个元素外,每个元素都有唯一的前驱;
4)除最后一个元素外,每个元素都有唯一的后继。
3.分类:
1)顺序存储的线性表:物理地址的连续存储;
2)链式存储的线性表:逻辑地址的连续存储。
数据结构 | 优点 | 缺点 |
数组 | 通过索引可以直接访问 | 在初始化时就需要知道元素的数量 |
链表 | 使用空间大小和元素成正比 | 需要通过引用访问任意元素 |
二、抽象数据类型(API)
template<typename Item> class slist{ | |
slist() | 创建一个空链表 |
void push(Item item) | 尾结点添加一个元素 |
Item pop() | 头节点删除一个元素 |
intsize() | 链表长度 |
bool Empty() | 单链表是否为空 |
void trave() | 遍历单链表 |
void clear() | 清空单链表 |
bool find(Item item) | 查找元素是否存在 |
void insert(Item item,int n) | 插入元素(元素,位序) |
Item delete_1(int n) | 按位序删除元素 |
void delete_2(Item item) | 按元素值删除元素 |
void reverse()}; | 单链表反序 |
三、代码片段
(以下为无哨兵的类型)
链表节点的构建:
结点是一个递归类型的数据结构,有两个域:
1.为可能含有任意数据类型的数据域data
2.为指向下一节结点的的指针域next
class Node
{
friend class slist;
Item data;
Node* next;
};
单链表的构建:
单链表分为private和public两部分。
private内为限制访问的数据分别是:链表头结点Node* first,尾结点Node* last,
以及记录链表长度的计数器int N;
public内为可访问的数据,即为上所写的调用函数。
以及较为简单的Empty()函数和size()函数
template <typename Item>
class slist
{
private:
Node* first = NULL;
Node* last = NULL;
int N = 0;
public:
bool Empty(){return N==0;}
int size(){return N;}
单链表添加元素:
这里采用的是尾结点添加法,为的是方便后续遍历等操作的进行。
主要步骤为:
1.申请新结点oldlast,指向原本last的空间;
2.last重新申请空间,存储新元素item;
3.若last为第一个进链的结点,应将first也指向该结点,
否则oldlast的next域指向last结点
4.计数器N加一。
void push(Item item)
{
Node* oldlast = last;
last = new Node();
last->data = item;
if (Empty())first = last;
else oldlast->next = last;
N++;
}
单链表删除元素:
这里是从链首删除元素,需要特别注意的是考虑链为空时的情况。
步骤:
1.申请结点指针curr指向头结点,申请数据Item类型储存first的数据
2.头指针后移,删除curr;
3.计数器N减一,返回头结点元素
Item pop()
{
if (Empty())exit(0);//如果链表为空,结束进程
Item item = first->data;
Node* curr = first;
first = first->next;
delete curr;
N--;
return item;
}
单链表遍历:
这里需注意不能直接移动first指针,所以需要重新申请指针结点。
步骤:
1.申请指针结点指向first
2.在指针不为空的前提下,输出每一次的data域,并将指针后移
void trave()
{
Node* curr = first;
while (curr != NULL)
{
cout << curr->data << " ";
curr = curr->next;
}
cout << endl;
}
清空链表:
步骤:
1.在first不为空的前提下,申请结点指针curr指向first,first指针后移
2.删除curr内数据,计数器N减一。
void clear()
{
while (first != NULL)
{
Node* curr = first;
first = first->next;
delete curr;
N--;
}
}
查找元素是否存在:
遍历单链表,若有相应元素输出true,否则输出false
bool find(Item item)
{
Node* curr = first;
for (int i = 0; i < N; i++)
if (curr->data == item)return true;
else curr = curr->next;
return false;
}
插入元素:
首先要考虑边界问题,防止插入元素越界
在没有哨兵的情况,头结点需单独考虑用头插法,其余用尾插法
步骤:
1.申请两个指针,一个指向插入位置的前一元素,一个指向待插入元素
2.当插入为头结点时,二指针后继指向头结点,头结点指向二指针
3.不为头结点时,用(单链表添加元素)中所用尾插法插入
(此处还得考虑尾结点插入,需要后移last指针)
4.计数器加一
void insert(string item, int n)//插入元素,插入位序
{
if (n<0 || n>N)exit(0);
Node* curr_2 = new Node();
curr_2->data = item;
if (Empty())last = first=curr_2;//链表为空,构建链表
else if (n==0){
curr_2->next = first;
first =curr_2;
}
else{
Node* curr = first;
for (int i = 0; i < n - 1; i++)
curr = curr->next;
curr_2->next = curr->next;
curr->next = curr_2;
if (n == N)last = curr_2;
}
N++;
}
按位序删除元素:
与插入所需考虑基本相同,只需将插入步骤换为删除
中间结点删除步骤:
1.指针一寻找删除元素前一位置的元素,指针二指向删除元素;
2.指针一的后继指向指针二的后继,
3.记录指针二元素,删除指针二
string delete_1(int t)//按位置删除
{
if (t<0 || t>=N)exit(0);
Node* curr = first;
string item;
if (t == 0){
item = first->data;
first = first->next;
delete curr;
}
else{
for (int i = 0; i < t - 1; i++)
curr = curr->next;
Node* curr_2 = curr->next;
curr->next = curr_2->next;
item = curr_2->data;
if (t == N - 1)last = curr;
delete curr_2;
}
N--;
return item;
}
按元素值删除元素:
同样也需要单独考虑头结点,因为可能有多个删除元素,所以这里选择构造一个哨兵。
同时一个标准变量FF记录是否删除头结点。删除其余结点的方法与上一部相同。
void delete_2(string item)//按值删除元素
{
Node* curr=new Node() ;
curr->next = first;
Node* curr_2 = first;
int size = N;
bool FF=0;//判断头指针是否被删除
for (int i = 0; i < size; i++)
{
if (curr_2->data == item)
{
if (!FF)first = first->next;
curr->next = curr_2->next;
cout << i << " ";
if (i == size-1)last = curr;
delete curr_2;
N--;
}
else{
if (!FF)FF = 1;
curr = curr->next;
}
curr_2 = curr->next;
}
cout << endl;
}
单链表的反序:
具体步骤就是将first的后继结点依次取出,然后头插法插入链表。
void reverse()//链表反序
{
Node* curr = first;
Node* curr_2 = first->next;
while (curr_2)
{
curr->next = curr_2->next;
curr_2->next = first;
first = curr_2;
curr_2 = curr->next;
}
last = curr;
}
带哨兵的单链表:
就链表而言,在插入、删除方面带哨兵要方便很多。
#include<iostream>
#include<string>
using namespace std;
class slist
{
class Node
{
public:
friend class slist;
string data;
Node* next;
};
private:
Node* first=new Node();
Node* last=NULL;
int N=0;
public:
bool Empty(){ return N == 0; }
int size(){ return N; }
void push(string item);
string pop();
void trave();
void clear();
bool find(string item);
void insert(string item, int n);
string delete_1(int t);
void delete_2(string item);
void reverse();
};
void slist::push(string item)
{
Node* oldlast = last;
last = new Node();
last->data = item;
if(Empty())first->next= last;
else oldlast->next = last;
N++;
}
string slist::pop()
{
if (Empty())exit(0);
Node* curr = first->next;
string item = curr->data;
first->next = curr->next;
delete curr;
N--;
return item;
}
void slist::trave()
{
Node* curr = first->next;
while (curr)
{
cout << curr->data << " ";
curr = curr->next;
}
cout << endl;
}
void slist::clear()
{
while (first->next)
{
Node* curr = first->next;
first->next = curr-> next;
delete curr;
N--;
}
}
bool slist::find(string item)
{
Node* curr = first->next;
for (int i = 0; i < N; i++)
if (curr->data == item)return true;
else curr = curr->next;
return false;
}
void slist::insert(string item, int n)
{
if (n<0 || n>N)exit(0);
Node* curr = first;
Node* curr_2 = new Node();
curr_2->data = item;
for (int i = 0; i <n; i++)
curr = curr->next;
curr_2->next = curr->next;
curr->next = curr_2;
if (n == N)last = curr_2;
N++;
}
string slist::delete_1(int t)
{
if (t<0 || t>=N)exit(0);
Node* curr = first;
for (int i = 0; i <= t-1; i++)
curr = curr->next;
Node* curr_2 = curr->next;
string item = curr_2->data;
curr->next = curr_2->next;
if (t == N - 1)last = curr;
delete curr_2;
N--;
return item;
}
void slist::delete_2(string item)
{
Node* curr = first;
Node* curr_2 = curr->next;
for (int i = 0, size = N; i < size; i++)
{
if (curr_2->data == item)
{
curr->next = curr_2->next;
cout << i << " ";
delete curr_2;
N--;
if (i == size - 1)last = curr;
}
else curr = curr->next;
curr_2 = curr->next;
}
cout << endl;
}
void slist::reverse()
{
Node* curr = first->next;
while (curr->next)
{
Node* curr_2 = curr->next;
curr->next = curr_2->next;
curr_2->next = first->next;
first->next = curr_2;
}
last = curr;
}
//测试用例
int main()
{
slist p;
string item;
int m, n, t;
cout << "**********************************" << endl;
cout << "0.查看菜单. 1.进链表." << endl;
cout << "2.出链表. 3.遍历链表." << endl;
cout << "4.链表是否为空. 5.链表长." << endl;
cout << "6.清空链表. 7.查询值是否存在." << endl;
cout << "8.插入元素. 9.按位置删除元素." << endl;
cout << "10.按值删除元素. 11.链表反序." << endl;
cout << "**********************************" << endl;
while (cin >> m)
{
switch (m)
{
case 0:
cout << "**********************************" << endl;
cout << "0.查看菜单. 1.进链表." << endl;
cout << "2.出链表. 3.遍历链表." << endl;
cout << "4.链表是否为空. 5.链表长." << endl;
cout << "6.清空链表. 7.查询值是否存在." << endl;
cout << "8.插入元素. 9.按位置删除元素." << endl;
cout << "10.按值删除元素. 11.链表反序." << endl;
cout << "**********************************" << endl;
break;
case 1:
cout << "输入进表元素:" << endl;
cin >> item;
p.push(item);
break;
case 2:
cout << "出表元素为:" << p.pop() << endl;
break;
case 3:
cout << "遍历链表:" << endl;
p.trave();
break;
case 4:
if (p.Empty())cout << "队为空" << endl;
else if (!p.Empty()) cout << "队不为空" << endl;
break;
case 5:
cout << "链表长为:" << p.size() << endl;
break;
case 6:
cout << "清空链表." << endl;
p.clear();
break;
case 7:
cout << "输入查询值:" << endl;
cin >> item;
if (!p.find(item))cout << "不存在." << endl;
else if (p.find(item)) cout << "存在" << endl;
break;
case 8:
cout << "输入插入元素和位序:" << endl;
cin >> item >> n;
p.insert(item, n);
break;
case 9:
cout << "输入删除元素位序:" << endl;
cin >> t;
cout << "删除元素为:" << p.delete_1(t) << endl;
break;
case 10:
cout << "输入删除元素的值:" << endl;
cin >> item;
cout << "被删除元素的位序为:" << endl;
p.delete_2(item);
break;
case 11:
cout << "反序链表." << endl;
p.reverse();
break;
}
}
system("pause");
return 0;
}