1. 线性表的链式存储
不要求逻辑上相邻的两个元素物理上也相邻,通过"链"建立起数据之间的逻辑关系。线性表的链式存储结构的特点是==用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。==这就意味着,这些元素可以存在内存未被占用的任意位置。
链表的定义是递归的,它或者为空null,或者指向另一个节点node的引用,这个节点含有下一个节点或链表的引用,线性链表的最后一个结点指针为“空”(通常用NULL或“^”符号表示)。
插入、删除不需要移动数据元素,只需要修改"链"
template<class DataType>
struct Node
{
DataType data; //存储数据
Node<DataType> *next; //存储下一个结点的地址
};
结点由存放数据元素的数据域和存放后继结点地址的指针域组成。
2. 链式存储结构的抽象数据类型 (c里面的L应该是第一个空节点?第一个空节点即头结点序号是0!!)
2.1 模板
c++
template<class DataType>
class LinkList
{
public:
LinkList(); //默认构造
LinkList(DataType a[], int n); // 有参构造
~LinkList(); //析构函数
int Length(); //参数
DataType Get(int i); //按位置查找
int Locate(DataType x); //按值查找
void Insert(int i, DataType x); //插入
DataType Delete(int i); //删除
void PrintList(); //打印输出
private:
Node<DataType> *first; //头指针
};
c语言
List L;
List MakeEmpty(); //初始化链表
int Length(List L); // 以遍历链表的方法求链表长度
List FindKth(int K,List L); // 按序号查找
List Find(ElementType X,List L); // 按值查找
List Insert(ElementType X,int i,List L); //将 X 插入到第 i-1(i>0) 个结点之后
List Delete(int i,List L); // 删除第 i(i>0) 个结点
void Print(List L); // 输出链表元素
2.1.1 特点
用一组任意的存储单元存储线性表的数据元素, 这组存储单元可以存在内存中未被占用的任意位置
顺序存储结构每个数据元素只需要存储一个位置就可以了,而链式存储结构中,除了要存储数据信息外,还要存储它的后继元素的存储地址
2.2 初始化,构造
2.2.1 无参构造
生成只有空节点的空链表
c++
template<class DataType>
LinkList<DataType>::LinkList()
{
first = new Node<DataType>;
first->next = NULL;
}
c
// 初始化链表
List MakeEmpty(){
List L = (List)malloc(sizeof(struct LNode));
L = NULL;
return L;
}
2.3 头插法构造单链表
头插法是每次将新申请的结点插在头结点后面。
c++
template<class DataType>
LinkList<DataType>::LinkList(DataType a[], int n)
{
first = new Node<DataType>;
first->next = NULL;
for (int i = 0; i < n; i++)
{
Node<DataType> *s = new Node<DataType>;
s->data = a[i];
s->next = first->next; //这边这个first->next;指的是原来还没有插入的时候的节点的数据,这个时候正好就把要插入的s节点指向了s后面的节点的数据了。
first->next = s;
}
}
c
/* 插入
1. 用 s 指向一个新的结点
2. 用 p 指向链表的第 i-1 个结点
3. s->Next = p->Next,将 s 的下一个结点指向 p 的下一个结点
4. p->Next = s,将 p 的下一结点改为 s */
List Insert(ElementType X,int i,List L){
List p,s;
if(i == 1){ // 新结点插入在表头 ,如果插入的节点是头结点
s = (List)malloc(sizeof(struct LNode)); //新插入的节点
s->Data = X; //x是数据域
s->Next = L;
return s; //插入的结点为头结点
}
p = FindKth(i-1,L); // 找到第 i-1 个结点,
if(!p){ // 第 i-1 个结点不存在
printf("结点错误");
return NULL;
}else{ //如果插入的不是头结点!!!!!!!!!也就是说随机插入的话
s = (List)malloc(sizeof(struct LNode));
s->Data = X;
s->Next = p->Next; //将 s 的下一个结点指向 p 的下一个结点 这边这个p->next;指的是原来还没有插入的时候的节点的数据,这个时候正好就把要插入的s节点指向了s后面的节点的数据了。
p->Next = s; // 将 p 的下一结点改为 s
return L;
}
}
2.4 尾插法构造单链表
尾插法就是每次将新申请的结点插在终端节点的后面
template<class DataType>
LinkList<DataType>::LinkList(DataType a[], int n)
{
first = new Node<DataType>;
Node<DataType> *r = first;
for (int i = 0; i < n; i++)
{
Node<DataType> *s = new Node<DataType>;
s->data = a[i];
r->next = s;
r = s;
}
r->next = NULL;
}
2.5 中间插入
单链表在插入过程中需要注意分析在表头、表中间、表尾的三种情况,由于单链表带头结点,这三种情况的操作语句一致,不用特殊处理,时间复杂度为 O(n)
template<class DataType>
void LinkList<DataType>::Insert(int i, DataType x)
{
first = new Node<DataType>;
Node<DataType> *p = first; //???????????
int count = 0;
while (p != NULL && count<i - 1) //count<i - 1!!!!!!!!
{
p = p->next;
count++;
}
if (p == NULL) throw "Location";
else {
Node<DataType> *s = new Node<DataType>;
s->data = x;
s->next = p->next; // p->next是未插入的时候指向的下一个数据
p->next = s;
}
}
总结插入元素:
还是c的代码比较牛,这样直接融会贯通了。
2.6 析构函数
单链表类中的结点是用new申请的,在释放的时候无法自动释放,所以,析构函数要将单链表中的结点空间释放
template<class DataType>
LinkList<DataType>::~LinkList()
{
while (first != NULL)
{
Node<DataType>* q = first;
first = first->next;
delete q;
}
}
C语言就需要析构了!。。
2.7 计算长度
单链表中不能直接求出长度,所以我们只能将单链表扫描一遍,所以时间复杂度为O(n)
C++
template<class DataType>
int LinkList<DataType>::Length()
{
Node<DataType>* p = first->next;
int count = 0;
while (p != NULL)
{
p = p->next; //再次指向下一个
count++;
}
return count;
}
C
//求表长
int Length(List L){
List p = L;
int len=0;
while(p){ // 当 p 不为空
p = p->Next;
len++;
}
return len;
}
2.8 按位查找
单链表中即使知道节点位置也不能直接访问,需要从头指针开始逐个节点向下搜索,平均时间性能为O(n) ,单链表是顺序存取结构
template<class DataType>
DataType LinkList<DataType>::Get(int i) // i代表的是 第几个
{
Node<DataType>* p = first->next;
int count = 1;
while (p != NULL && count<i)
{
p = p->next;
count++;
}
if (p == NULL) throw "Location";
else return p->data;
}
// 按序查找
List FindKth(int K,List L){
List p = L;
int i = 1; //从 1 开始
while(p && i<K){
p = p->Next;
i++;
}
if(i == K) // 找到了
return p;
else // 未找到
return NULL;
}
2.9 按值查找
单链表中按值查找与顺序表中的实现方法类似,==对链表中的元素依次进行比较,平均时间性能为O(n) ==
template<class DataType>
int LinkList<DataType>::Locate(DataType x)
{
Node<DataType> *p = first->next; //第一个空节点后面的带数据域的节点!!!!! 注意这个序号!
int count = 1;
while (p != NULL)
{
if (p->data == x) return count;
p = p->next;
count++;
}
return 0;
}
// 按值查找
List Find(ElementType X,List L){
List p = L;
while(p && p->Data!=X)
p = p->Next;
// 找到了,返回 p
// 未找到,返回 NULL,此时 p 等于 NULL
return p;
}
2.10 删除
删除操作时需要注意表尾的特殊情况,此时虽然被删结点不存在,但其前驱结点却存在。因此仅当被删结点的前驱结点存在且不是终端节点时,才能确定被删节点存在,时间复杂度为O(n) .
template<class DataType>
DataType LinkList<DataType>::Delete(int i) //注意::这个p是要删除的节点q的前一个节点
{
Node<DataType> *p = first;
int count = 0;
while (p != NULL && count<i - 1)
{
p = p->next;
count++;
}
if (p == NULL || p->next == NULL) throw "Location";
else {
Node<DataType> *q = p->next;
int x = q->data;
p->next = q->next;
return x;
}
}
c
/* 删除
1. 用 p 指向链表的第 i-1 个结点
2. 用 s 指向要被删除的的第 i 个结点
3. p->Next = s->Next,p 指针指向 s 后面
4. free(s),释放空间
*/
List Delete(int i,List L){
List p,s;
if(i==1){ //如果要删除头结点
s = L;
if(L) // 如果不为空
L = L->Next;
else
return NULL;
free(s); // 释放被删除结点
return L;
}
p = FindKth(i-1,L); // 查找第 i-1 个结点
if(!p || !(p->Next)){ // 第 i-1 个或第 i 个结点不存在
printf("结点错误");
return NULL;
}else{
s = p->Next; // s 指向第 i 个结点
p->Next = s->Next; //从链表删除
free(s); // 释放被删除结点
return L;
}
}
2.11 遍历
遍历单链表时间复杂度为O(n) .
template<class DataType>
void LinkList<DataType>::PrintList()
{
Node<DataType> *p = first->next;
while (p != NULL)
{
cout << p->data << endl;
p = p->next;
}
}
c
// 输出链表元素
void Print(List L){
List t;
int flag = 1;
printf("当前链表为:");
for(t = L;t;t =t->Next){
printf("%d ",t->Data);
flag = 0;
}
if(flag)
printf("NULL");
printf("\n");
}
2.12 测试
c++
int main()
{
LinkList<int> p;
p.Insert(1, 6);
p.Insert(2, 9);
p.PrintList();
p.Insert(2, 3);
p.PrintList();
cout << p.Get(2) << endl;
cout << p.Locate(9) << endl;
cout << p.Length() << endl;
p.Delete(1);
p.PrintList();
return 0;
}
c
int main(){
L = MakeEmpty();
Print(L);
L = Insert(11,1,L);
L = Insert(25,1,L);
L = Insert(33,2,L);
L = Insert(77,3,L);
Print(L);
printf("当前链表长度为:%d\n",Length(L));
printf("此时链表中第二个结点的值是:%d\n",FindKth(2,L)->Data);
printf("查找22是否在该链表中:");
if(Find(22,L))
printf("是!\n");
else
printf("否!\n");
printf("查找33是否在该链表中:");
if(Find(33,L))
printf("是!\n");
else
printf("否!\n");
L = Delete(1,L);
L = Delete(3,L);
printf("----------删除后-----\n");
Print(L);
return 0;
}
3. 链式存储的优缺点
优点:
插入、删除不需移动其他元素,只需改变指针.
链表各个节点在内存中空间不要求连续,空间利用率高
缺点:
查找需要遍历操作,比较麻烦