一、定义:零个或多个数据元素的有限序列。
二、线性表的顺序存储结构
1、定义:用一段地址连续的存储单元一次存储线性表,顺序存储结构的线性表逻辑结构和物理结构相同,即逻辑上相邻的元素物理上也相邻。
2、优点:
- 无须为表示表中元素之间的逻辑关系二增加额外的存储空间。
- 可以快速的存取表中任意位置的元素
3、缺点:
- 插入和删除操作需要移动大量的元素
- 当线性表长度比较大时难以确定存储空间。
- 造成存储空间的“碎片”
4、代码实例(C#版)
public class SeqList<T>
{
private T[] data;
public int maxsize { get; set; }
/// <summary>
/// 最后一个元素的索引
/// </summary>
public int last { get; set; }
/// <summary>
/// 索引器
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
/*public T this[int index]
{
get { return data[index]; }
set { data[index] = value; }
}*/
/// <summary>
/// 构造器 建立空表
/// </summary>
public SeqList(int max)
{
last = -1;
maxsize = max;
data = new T[maxsize];
}
/// <summary>
/// 判断线性表是否为空
/// </summary>
/// <returns></returns>
public bool ListEmpty()
{
if (last == -1)
return true;
else return false;
}
/// <summary>
/// 清空线性表
/// </summary>
public void ClearList()
{
last = -1;
}
/// <summary>
/// 返回索引为i的元素
/// </summary>
/// <param name="i"></param>
/// <param name="t"></param>
public T GetElem(int i)
{
if (ListEmpty())
return default(T);
if (i > maxsize || i < 1)
return default(T);
return data[i - 1];
}
/// <summary>
/// 查找值为e的元素
/// 找到返回下标 找不到返回-1
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
public int LocateElem(T e)
{
if (!ListEmpty())
{
for (int i = 0; i < maxsize; i++)
{
if (e.Equals(data[i]))
return i;
}
return -1;
}
else return -1;
}
/// <summary>
/// 将e插入表中第i个元素
/// </summary>
/// <param name="i"></param>
/// <param name="e"></param>
/// <returns></returns>
public bool ListInsert(int i, T e)
{
i = i - 1;
if (last == maxsize - 1)
return false;
if (i < 0 || i > last + 1)
return false;
if (i <= last +1)
{
for (int k = last; k > i - 1; k--)
data[k + 1] = data[k];
}
data[i] = e;
last++;
return true;
}
/// <summary>
/// 删除第i个元素 返回被删除的元素
/// </summary>
/// <param name="i"></param>
/// <returns></returns>
public T ListDelete(int i)
{
i = i - 1;
T e;
if (last == -1)
return default(T);
if (i < 0 || i > last)
return default(T);
e = data[i];
for (int j = i; j > last; j++)
{
data[j] = data[j + 1];
}
last--;
return e;
}
/// <summary>
/// 获取线性表的元素个数
/// </summary>
/// <returns></returns>
public int ListLength()
{
return last + 1;
}
}
三、线性表的链式存储结构
1、存储结构定义:表中的元素除了要存储数据元素信息外还要存储它的后继地址,我们把存储数据信息的元素称为数据域,存储地址的称为指针域,元素称为节点。指向第一个节点的指针称为头指针,第一个节点称为头节点。
2、头指针:
- 头指针是指链表中指向第一个节点的指针,若链表有头节点则指向头节点。
- 头指针具有标识作用,所以常常用头指针冠以链表的名字
- 无论链表是否为空头指针都不为空,它时链表必要的元素
3、头节点
- 头节点是为了操作的统一和方便而设立的,放在第一个节点之前,其数据域一般无意义
- 头节点使得对第一个元素的插入删除变得和其他节点统一
- 头节点不一定是链表的必要元素
4、代码描述:
public class Node<T>
{
/// <summary>
/// 数据域
/// </summary>
public T e;
/// <summary>
/// 指针域
/// </summary>
public Node<T> next;
/// <summary>
/// 构造器
/// </summary>
/// <param name="t"></param>
public Node(T t)
{
e = t;
next = null;
}
public Node()
{
e = default(T);
next = null;
}
}
public class LinkedList<T>
{
/// <summary>
/// 头指针
/// </summary>
private Node<T> node;
/// <summary>
/// 链表长度
/// </summary>
private int Lenght;
/// <summary>
/// 构造器
/// </summary>
public LinkedList ()
{
node = null;
Lenght = 0;
}
/// <summary>
/// 获取链表长度
/// </summary>
/// <returns></returns>
public int GetLenght()
{
return Lenght;
}
/// <summary>
/// 情空链表
/// </summary>
public void Clear()
{
node = null;
Lenght =0;
}
/// <summary>
/// 判断链表是否为空
/// </summary>
/// <returns></returns>
public bool IsEmpty()
{
if (node == null)
return true;
else return false;
}
/// <summary>
/// 在表的末尾添加节点
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
public int Append(T e)
{
Node<T> p = new Node<T>();
p.e = e;
p.next = null;
Node<T> q = new Node<T>();
if(node==null)
{
node = p;
}
else
{
q = node;
while (q.next !=null)
{
q = q.next;
}
q.next = p;
}
Lenght++;
return Lenght;
}
/// <summary>
/// 在链表的第i个节点插入一个值为item的节点
/// </summary>
/// <param name="item">插入的值</param>
/// <param name="i">插入的位置</param>
/// <returns></returns>
public bool Insert(T item, int i)
{
Node<T> q = new Node<T>();
Node<T> p = new Node<T>();
Node<T> r = new Node<T>();
p.e = item;
if (IsEmpty() || i < 0)
{
return false;
}
if (i == 1)
{
p.next = node;
node = p;
Lenght++;
return true;
}
int j = 1;
while (q.next != null && j < i)
{
r = q;
q = q.next;
j++;
}
if(j==i)
{
p.next = q;
r.next = p;
Lenght++;
return true;
}
else
{
return false;
}
}
/// <summary>
/// 删除位置i的节点
/// </summary>
/// <param name="i">要删除的节点的位置</param>
/// <returns>位置为i的节点</returns>
public Node<T> ListDelete(int i)
{
int j = 1;
Node<T> p = new Node<T>();
p = node;
if (i > Lenght || i < 1)
return null;
while(p.next !=null&&j<i)
{
p = p.next;
j++;
}
if (j == i)
{
Node<T> r = new Node<T>();
r = p.next;
p.next = r.next;
Lenght--;
return r;
}
else return null;
}
}
四、单链表结构与顺序存储结构对比
类型 | 顺序表 | 单链表 |
---|---|---|
存储方式分配 | 顺序储存结构用一段连续的存储单元依次存储线性表的数据元素 | 采用链式存储结构用一组任意的存储单元存放线性表的元素 |
时间性能 | 查找:O(1) 插入和删除:O(n)需要平均移动表长一半的元素 | 查找:O(N) 插入删除:知道某位置的指针后插入和删除时间为O(1) |
空间性能 | 需要预分配存储空间,分大了浪费,分小了 上溢 | 不需要预分配空间大小,元素个数也不限制 |
五、静态链表:用数组描述的链表
1、优缺点:
优点 | 缺点 |
在插入和删除操作时,不需要移动元素,只需要修改游标,改进了在顺序存储结构中的插入和删除操作需要移动大量元素的缺点 | 1、没有解决大量插入元素时,表长难以确定的问题 2、失去顺序存储结构随机存储的特性 |
六、循环链表:将单链表中终端的空指针改为指向表头节点,使得整个链表形成一个环的链表称为循环链表
1、循环链表解决了从任意一个节点出发遍历整个链表的问题。
2、循环链表使用指向终端的尾指针表示这个链表
3、循环结束标志:p->next为头节点。
4、合并两个循环链表:
//rearA rearB是两个循环链表
//rearA rearB为两个表的尾节点
//表B的头节点被丢弃
//合并这两个循环链表的步骤为
p=rearA->next; //1、A表的头节点
rearA->next=rearB->next->next; //2、使A表的尾节点的next指向B表的第一个节点
rearB->next=p; //3、使B表的尾节点的next指向A表的头节点
5、双向链表:在单链表的每一个节点再加上一个前驱指针,指向其前驱结点,就构成双向链表。
1、头指针的前驱指针:双向链表的头节点的前驱指针指向其尾节点
2、尾节点的后继指针:双向链表的尾节点的后继指针指向其头节点
3、插入节点:
4、删除节点: