一、线性表的定义:
线性表(List):零个或多个数据元素的有限序列。
若将线性表记为(a1,……,ai-1,ai,ai+1,……,an),则表中ai-1领先于ai,ai领先于ai+1,称ai+1是ai的直接前驱元素,ai+1是ai的直接后继元素。当i=1,2,……,n-1时,ai有且只有一个直接后继,当i=2,3,……,n时,ai有且仅有一个直接前驱。
二、线性表的顺序储存结构:
1、概念:指用一段地址连续的存储单元一次存储线性表的数据结构;
地址:存储器中的每个存储单元都有自己的编号,这个编号称为地址
C#实现:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SeqList
{
public interface ILinearList<T>
{
/// <summary>
/// 数组长度
/// </summary>
int Length
{
get;
}
/// <summary>
/// 数组是否为空
/// </summary>
bool IsEmpty();
/// <summary>
/// 清空表
/// </summary>
void Clear();
/// <summary>
/// 通过索引获取元素
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
T GetItem(int index);
/// <summary>
///返回数据元素的索引
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
int LocateItem(T t);
/// <summary>
/// 将数据插入到指定位置
/// </summary>
/// <param name="item"></param>
/// <param name="index"></param>
void Insert(T item, int index);
/// <summary>
/// 在数组末尾添加元素
/// </summary>
/// <param name="item"></param>
void Add(T item);
/// <summary>
/// 删除指定索引的元素
/// </summary>
/// <param name="index"></param>
void Delete(int index);
}
}
/*************************************
*************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SeqList
{/// <summary>
/// 线性表顺序结构
/// </summary>
/// <typeparam name="T"></typeparam>
class SequentialList<T> : ILinearList<T>
{
private T[] list;
private int length = 0;
public int MaxSize
{
private set;
get;
}
public int Length
{
get { return length; }
}
public SequentialList(int maxSize)
{
if (maxSize <= 0)
{
throw new Exception("the maxSize can not be less than zero");
}
MaxSize = maxSize;
list = new T[maxSize];
}
public bool IsEmpty()
{
return length == 0;
}
public void Clear()
{
length = 0;
}
public T GetItem(int index)
{
return list[index];
}
public int LocateItem(T t)
{
for (int i = 0; i < list.Length; i++)
{
if (list[i].Equals(t))
return i;
}
return -1;
}
public void Insert(T item, int index)
{
if (isFull())
{
throw new Exception("This linear list is full");
}
if (index < 0 || index > length)
{
throw new Exception("Location exception");
}
length++;
for (int i = length - 1; i > index; i--)
{
list[i] = list[i - 1];
}
list[index] = item;
}
public void Add(T item)
{
if (isFull())
{
throw new Exception("This linear list is full");
}
length++;
list[length - 1] = item;
}
public void Delete(int index)
{
if (index < 0 || index > length - 1)
{
throw new Exception("Location exception");
}
length--;
for (int i = index; i < length; i++)
{
list[i] = list[i + 1];
}
}
bool isFull()
{
return length >= MaxSize;
}
}
}
线性表顺序储存结构的优缺点
优点 | 缺点 |
---|---|
无需为表示表中元素之间的逻辑关系而增加额外的存储空间 | 插入和删除操作需要移动大量元素当线性表长度版画较大时,难以确 |
可以快速的存取表中的任一位 置的元素 | 定存储空间的容量s造成存储空间的碎片 |
三、顺序表链式存储结构:
1、结点(Node):为了表示每个数据元素ai与其直接后继元素ai+1之间的逻辑关系,对数据元素ai来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域,指针域中存储的信息称做指针或链,这两部分信息组成的数据元素ai的存储映像,称为结点(Node)。
2、n个结点(ai的存储映像)链结成一个链表,即为线性表(a1,a2,……,an)的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以也叫单链表。
3、头指针:链表中的第一个结点的存储位置叫做头指针;
4、头结点:为了方便对链表进行操作,会在单链表的第一个结点前附设一个结点,称为头结点。
头指针 头结点
? 头指针是指链表指向第一个结点的指 ? 头结点是为了操作的统一和方便
针,若链表有头结点,则是指向头结 而设立的,放在第一元素的结点
点的指针 之前,其数据域一般无意义(也
? 头指针具有标识作用,所以常用头指 可以存放链表的长度)
针冠以链表的名字 ? 有了头结点,对在第一元素结点
? 无论链表是否为空,头指针均不为空 前插入结点和删除第一结点,其
头指针是链表的必要元素 操作与其它结点的操作就统一了
? 头结点不一定是链表的必须要素
C#实现:
/// <summary>
/// 单链表的结点
/// </summary>
/// <typeparam name="T"></typeparam>
public class Node<T>
{
private T data;//数据域
private Node<T> next;//指针 用来指向下一个结点
/// <summary>
/// 构造函数
/// </summary>
public Node()
{
data = default(T);
next = null;
}
public Node(T value)
{
data = value;
next = null;
}
public Node(T value, Node<T> next)
{
data = value;
this.next = next;
}
public Node(Node<T> next)
{
this.next = next;
}
private T Data { get { return data; } set { data = value; } }
private Node<T> Next { get { return next; }set { next = value; } }
}
结点由存放数据元素的数据域和存放后继结点地址的指针组成。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SeqList
{
/// <summary>
/// 线性表链式结构
/// </summary>
/// <typeparam name="T"></typeparam>
class LinkList<T> : ILinearList<T>
{
private Node<T> head;//存储一个头结点
public int Length => throw new NotImplementedException();
public LinkList()
{
head = null;
}
public T this[int index]
{
get
{
Node<T> temp = head;
for (int i = 0; i < index; i++)
{
temp = temp.Next;
}
return temp.Data;
}
}
public int GetLength()
{
if (head == null) return 0;
Node<T> temp = head;
int count = 1;
while (true)
{
if (temp.Next != null)
{
count++;
temp = temp.Next;
}
else
{
break;
}
}
return count;
}
public void Add(T item)
{
Node<T> newNode = new Node<T>(item);//根据新数据创建一个新结点
if (head == null)
{
head = newNode;
}
else
{//把新的结点放在链表尾部
Node<T> temp = head;
while (true)
{
if (temp.Next != null)
{
temp = temp.Next;
}
else
{
break;
}
}
temp.Next = newNode;
}
}
public void Clear()
{
head = null;
}
public T Delete(int index)
{
T data = default(T);
if (index == 0)
{
data = head.Data;
head = head.Next;
}
else
{
Node<T> temp = head;
for (int i = 1; i <= index; i++)
{
temp = temp.Next;
}
Node<T> preNode = temp;
Node<T> currentNode = temp.Next;
Node<T> nextNode = temp.Next.Next;
preNode.Next = nextNode;
}
return data;
}
public T GetItem(int index)
{
return this[index];
}
public void Insert(T item, int index)
{
Node<T> newNode = new Node<T>(item);
if (index == 0)//插入到头结点
{
newNode.Next = head;
head = newNode;
}
else
{
Node<T> temp = head;
for (int i = 1; i <= index - 1; i++)
{
//让temp向后移动一个位置
temp = temp.Next;
}
Node<T> preNode = temp;
Node<T> currentNode = temp.Next;
preNode.Next = newNode;
newNode.Next = currentNode;
}
}
public bool IsEmpty()
{
return head == null;
}
public int LocateItem(T value)
{
Node<T> temp = head;
if (temp == null)
{
return -1;
}
else
{
int index = 0;
while (true)
{
if (temp.Data.Equals(value))
{
return index;
}
else
{
if (temp.Next != null)
{
temp = temp.Next;
}
else
{
break;
}
}
}
return -1;
}
}
public void Reverse()
{
throw new NotImplementedException();
}
/// <summary>
/// 单链表的结点
/// </summary>
/// <typeparam name="T"></typeparam>
public class Node<T>
{
private T data;//数据域
private Node<T> next;//指针 用来指向下一个结点
/// <summary>
/// 构造函数
/// </summary>
public Node()
{
data = default(T);
next = null;
}
public Node(T value)
{
data = value;
next = null;
}
public Node(T value, Node<T> next)
{
data = value;
this.next = next;
}
public Node(Node<T> next)
{
this.next = next;
}
public T Data { get { return data; } set { data = value; } }
public Node<T> Next { get { return next; } set { next = value; } }
}
}
}
5、单链表结构和顺序储存结构的对比:
存储分配方式 | 时间性能 | 空间性能 |
---|---|---|
顺序存储结构用一段连续的存储单元依次存储线性表的数据元素 单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素 | 查找 :顺序存储结构O(1)单链表 O(n) 插入和删除: 顺序存储结构需要平均移动表长一半的元素,时间为O(n) 单链表在查出某位置的指针后,插入和删除时间仅为O(1) | 顺序存储结构需要预先分配存储空,大了浪费,小了溢出 |
? ? ?
?
?
6、静态链表:
1、描述:首先让数组的元素都由两个数据与组成,data和cur。也就是说数组的每个下标都对应一个data和一个cur,数据域data,用来存放数据元素,而游标cur相当于链表的next指针,存放该元素的后继在数组中的下标。
这种用数组描述的链表叫做静态链表。
public class Link
{
public string data;
public int cur;
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LinkList
{
public class Link
{
public string data;
public int cur;//游标
}
public class LinkList
{
static readonly int MAXSIZE = 100;//数组最大长度
private int length = 0;
public int Length
{
get { return length; }
set { length = value; }
}
public static Link[] array;//声明静态数组
/// <summary>
/// 构造函数
/// </summary>
public LinkList()
{
array = new Link[MAXSIZE];
Init();//初始化
}
/// <summary>
/// 初始化静态链表
/// </summary>
private void Init()
{
for (int i = 0; i < MAXSIZE; i++)
{
array[i] = new Link();
array[i].cur = i + 1;//指向数组的下一个元素
}
}
/// <summary>
/// 返回第一个可用的位置
/// </summary>
/// <returns></returns>
private int Mallo()
{
int i = array[0].cur;
if (array[0].cur > 0)
{
array[0].cur = array[i].cur;
}
return i;
}
/// <summary>
/// 添加一个数据项
/// </summary>
/// <param name="i"></param>
public void Add(Link i)
{
int temp = Mallo();//找到第一个空位
array[temp].data = i.data;
array[temp].cur = array[0].cur;
addLength();
}
private void addLength()
{
length++;
}
private void deleteLength()
{
length--;
}
/// <summary>
/// 在指定位置插入数据
/// </summary>
/// <param name="index">指定位置</param>
/// <param name="l">要插入的数据</param>
public void Insert(int index, Link l)
{
if (index > array[0].cur)
{
return;
}
int freeLength = Mallo();
int temp = MAXSIZE - 1;
for (int i = 1; i < index-1; i++)
{
temp = array[temp].cur;
}
l.cur = array[temp].cur;
array[temp].cur = freeLength;
array[freeLength] = l;
addLength();
}
public void Delete(int index)
{
if (index >= array[0].cur)
{
return;
}
int k = array[MAXSIZE - 1].cur;
for (int i = 1; i < index-1; i++)
{
k = array[k].cur;
}
int temp = array[k].cur;
array[k].cur = array[temp].cur;
free(temp);
deleteLength();
}
private void free(int i)
{
array [i].cur = array[0].cur;
array[i].data = null;
array[0].cur = i;
}
}
}
静态链表的优缺点
优点 缺点
在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量元素的缺点 没有解决连续存储分配带来的表长难以确定的问题
失去了顺序存储结构随机存取的特性
7、循环链表
1、概念:将单链表中终端结点的指针端由空指针改为指向头结点,使整个单链表形称一个闭环,这种头尾相连的链表称为单循环链表,简称循环链表(Circular LinkedList)。
??
8、双向链表(Double Linked List)
1、概念:在单链表的每个结点中,在设置一个指向其前驱结点的指针域。所以在双向链表中每个结点都有两个指针域,一个指向其直接前驱,一个指向其直接后继。
9、双向循环链表: