只考虑线性表的基本操做,因此以C#接口的形式表示线性表,接口中的方法成员表示基本操做。node
public interface IListDS<T> { T this[int index] { get; } //索引器 int GetLength(); //求长度 void Clear(); //清空操做 bool IsEmpty(); //判断线性表是否为空 void Append(T item); //附加操做 void Insert(T item, int i); //插入操做 T Delete(int i); //删除操做 T GetElem(int i); //取表元 int Locate(T value); //按值查找 }
为了和.NET框架中的接口IList相区分,在IList后面加了“DS”,“DS”表示数据结构。
一、求长度:GetLength()
初始条件:线性表存在;
操做结果:返回线性表中全部数据元素的个数。数组
二、清空操做:Clear()
初始条件:线性表存在且有数据元素;
操做结果:从线性表中清除全部数据元素,线性表为空。数据结构
三、判断线性表是否为空:IsEmpty()
初始条件:线性表存在;
操做结果:若是线性表为空返回true,不然返回false。框架
四、附加操做:Append(T item)
初始条件:线性表存在且未满;
操做结果:将值为item的新元素添加到表的末尾。ide
五、插入操做:Insert(T item, int i)
初始条件:线性表存在,插入位置正确()(1≤i≤n+1,n为插入前的表长)。
操做结果:在线性表的第i个位置上插入一个值为item的新元素,这样使得原序号为i,i+1,…,n的数据元素的序号变为i+1,i+2,…,n+1,插入后表长=原表长+1。测试
六、删除操做:Delete(int i)
初始条件:线性表存在且不为空,删除位置正确(1≤i≤n,n为删除前的表长)。
操做结果:在线性表中删除序号为i的数据元素,返回删除后的数据元素。
删除后使原序号为i+1,i+2,…,n的数据元素的序号变为i,i+1,…,n-1,删除后表长=原表长-1。ui
七、取表元:GetElem(int i)
初始条件:线性表存在,所取数据元素位置正确(1≤i≤n,n为线性表的表长);
操做结果:返回线性表中第i个数据元素。this
八、按值查找:Locate(T value)
初始条件:线性表存在。
操做结果:在线性表中查找值为value的数据元素,其结果返回在线性表中首次出现的值为value的数据元素的序号,称为查找成功;不然,在线性表中未找到值为value的数据元素,返回一个特殊值表示查找失败。指针
/// <summary> /// 顺序表 /// </summary> /// <typeparam name="T"></typeparam> public class SeqList<T> : IListDS<T> { private int count = 0; //顺序表的容量 private T[] data; //数组,用于存储顺序表中的数据元素 private int last; //指示顺序表最后一个元素的位置 //索引器 public T this[int index] { get { return data[index]; } set { data[index] = value; } } public SeqList(int size) { data = new T[size]; count = 0; } public SeqList():this(10) { } public int GetLength() { return count; } public void Clear() { this.count = 0; } public bool IsEmpty() { return count == 0; } public void Append(T item) { if (count == data.Length)// 当前数组存满 { Console.WriteLine("List is full"); } else { data[count] = item; count++; } } public void Insert(T item, int i) { if (count == data.Length)// 当前数组存满 { Console.WriteLine("List is full"); } else { //这里由于从插入位置日后覆盖,第1个被覆盖索引数据就不见了。因此须要从最后面往前覆盖 for (int j = count-1; j >= i; j--) { data[j + 1] = data[j]; } data[i] = item; count++; } } public T Delete(int i) { T temp = data[i]; for (int j = i+1; j < count; j++) { data[j - 1] = data[j]; } count--; return temp; } public T GetElem(int i) { //索引存在 if (i >= 0 && i <= count-1) { return data[i]; } else { Console.WriteLine("索引不存在"); return default(T); } } public int Locate(T value) { for (int i = 0; i < data.Length; i++) { if (value.Equals(data[i])) { return i; } } return -1; } }
class Program { static void Main(string[] args) { SeqList<string> seqList = new SeqList<string>(); seqList.Append("123"); seqList.Append("456"); seqList.Append("789"); Console.Write("第一次加载:"); for (int i = 0; i < seqList.GetLength(); i++) { Console.Write($"{seqList[i]},"); } Console.WriteLine(); Console.WriteLine($"第0位置元素:{seqList.GetElem(0)}"); Console.WriteLine($"第0索引:{seqList[0]}"); Console.Write("Insert后的数据:"); seqList.Insert("999", 1); for (int i = 0; i < seqList.GetLength(); i++) { Console.Write($"{seqList[i]},"); } Console.WriteLine(); Console.Write("Delete(0)后的数据:"); seqList.Delete(0); for (int i = 0; i < seqList.GetLength(); i++) { Console.Write($"{seqList[i]},"); } Console.WriteLine(); Console.Write("清空后:"); seqList.Clear(); Console.WriteLine(seqList.GetLength()); Console.Read(); } }
在对顺序表进行插入和删除时,须要经过移动数据元素来实现,影响了运行效率。本节介绍线性表的另一种存储结构——链式存储(Linked Storage),这样的线性表叫链表(Linked List)。链表不要求逻辑上相邻的数据元素在物理存储位置上也相邻,所以,在对链表进行插入和删除时不须要移动数据元素,但同时也失去了顺序表可随机存储的优势。code
链表是用一组任意的存储单元来存储线性表中的数据元素(这组存储单元能够是连续的,也能够是不连续的)。那么,怎么表示两个数据元素逻辑上的相邻关系呢?即如何表示数据元素之间的线性关系呢?为此,在存储数据元素时,除了存储数据元素自己的信息外,还要存储与它相邻的数据元素的存储地址信息。这两部分信息组成该数据元素的存储映像(Image),称为结点(Node)。把存储据元素自己信息的域叫结点的数据域(Data Domain),把存储与它相邻的数据元素的存储地址信息的域叫结点的引用域(Reference Domain)。所以,线性表经过每一个结点的引用域造成了一根“链条”,这就是“链表”名称的由来。
若是结点的引用域只存储该结点直接后继结点的存储地址,则该链表叫单链表(Singly Linked List)。把该引用域叫 next。单链表结点的结构如图所示,图中 data 表示结点的数据域。
插入的状况解析
新建节点类,注意data与next
// <summary> /// 单链表节点 /// </summary> /// <typeparam name="T"></typeparam> public class Node<T> { public T data { get; set; } //(数据域),存储数据自己 public Node<T> next { get; set; } //(引用域)指针,用来指向下一个元素 public Node(T data) { this.data = data; this.next = null; } public Node(Node<T> next) { this.next = next; } public Node(T data, Node<T> next) { this.data = data; this.next = next; } }
/// <summary> /// 单链表 /// </summary> /// <typeparam name="T"></typeparam> class SingleLinkList<T> : IListDS<T> { private Node<T> header; public int GetLength() { Node<T> p = header; int len = 0; while (p != null) { ++len; p = p.next; } return len; } public void Clear() { header = null; } public bool IsEmpty() { return header == null; } public void Append(T item) { Node<T> newNode = new Node<T>(item); //若是头节点为空,新节点为头节点 if (header == null) { header = newNode; } else { Node<T> headNode = header; //下个节点为空时,说明是末尾节点。 while (headNode.next != null) { headNode = headNode.next; } //把须要添加的node,添加给末尾指针next headNode.next = newNode; } } public void Insert(T item, int i) { //从第1个节点开始 if (i == 0) { //头节点不容许插入 Console.WriteLine("List is empty or Position is error"); } else if (i == 1) { //若是是第1个节点 Node<T> newNode = new Node<T>(item); newNode.next = header.next; header.next = newNode; } else { Node<T> tempNode = header; int j = 1; //让temp从头开始向后移动到下一个位置,移动到插入前一个位置为止。 while (tempNode.next != null && j < i) { tempNode = tempNode.next; j++; } if (j == i) { Node<T> newNode = new Node<T>(item); //行插入节点 Node<T> preNode = tempNode; //前节点 Node<T> currentNode = tempNode.next; //当前节点 newNode.next = currentNode; //插入节点的指针 preNode.next = newNode; //前节点指针 } } } public T Delete(int i) { //从第1个节点开始 if (i == 0) { T current = header.data; header = header.next; return current; } else { Node<T> tempNode = header; int j = 1; //让temp从头开始向后移动到下一个位置,移动到插入前一个位置为止。 while (tempNode.next != null && j < i) { tempNode = tempNode.next; j++; } if (j == i) { Node<T> preNode = tempNode; //前节点 Node<T> currentNode = preNode.next; //前节点 Node<T> nexNode = tempNode.next.next; //下一个节点 preNode.next = nexNode; //前节点指针 return currentNode.data; } return default(T); } } public T GetElem(int i) { return this[i]; } public int Locate(T value) { if (IsEmpty()) { return -1; } else { Node<T> p = header; int i = 1; while (!p.data.Equals(value) && p.next != null) { p = p.next; ++i; } return i; } } public T this[int index] { get { Node<T> p = header; int len = 0; while (p != null && len < index) { ++len; p = p.next; } return p.data; } } public override string ToString() { StringBuilder builder = new StringBuilder(); builder.Append("单链表:"); Node<T> p = header; int len = 0; while (p != null) { builder.Append($"{p.data.ToString()},"); ++len; p = p.next; } return builder.ToString(); } }
class Program { static void Main(string[] args) { SingleLinkList<int> singleLink = new SingleLinkList<int>(); singleLink.Append(100); singleLink.Append(200); singleLink.Append(300); singleLink.Append(400); singleLink.Append(500); singleLink.Append(600); Console.WriteLine(singleLink.ToString()); singleLink.Insert(900, 3); Console.WriteLine("插入:900 index:3"); Console.WriteLine(singleLink.ToString()); Console.WriteLine("删除index:4,删除的数据为:" + singleLink.Delete(4)); Console.WriteLine(singleLink.ToString()); Console.WriteLine("如今长度:" + singleLink.GetLength()); Console.WriteLine("索引5的数据:" + singleLink[5]); } }
前面介绍的单链表容许从一个结点直接访问它的后继结点,因此, 找直接后继结点的时间复杂度是 O(1)。可是,要找某个结点的直接前驱结点,只能从表的头引用开始遍历各结点。若是某个结点的 Next 等于该结点,那么,这个结点就是该结点的直接前驱结点。也就是说,找直接前驱结点的时间复杂度是 O(n), n是单链表的长度。固然,咱们也能够在结点的引用域中保存直接前驱结点的地址而不是直接后继结点的地址。这样,找直接前驱结点的时间复杂度只有 O(1),但找直接后继结点的时间复杂度是 O(n)。若是但愿找直接前驱结点和直接后继结点的时间复杂度都是 O(1),那么,须要在结点中设两个引用域,一个保存直接前驱结点的地址,叫 prev,一个直接后继结点的地址,叫 next,这样的链表就是双向链表(Doubly Linked List)。双向链表的结点结构示意图如图所示。
public class DbNode<T> { private T data; //数据域 private DbNode<T> prev; //前驱引用域 private DbNode<T> next; //后继引用域 //构造器 public DbNode(T val, DbNode<T> p) { data = val; next = p; } //构造器 public DbNode(DbNode<T> p) { next = p; } //构造器 public DbNode(T val) { data = val; next = null; } ![
有些应用不须要链表中有明显的头尾结点。在这种状况下,可能须要方便地从最后一个结点访问到第一个结点。此时,最后一个结点的引用域不是空引用,而是保存的第一个结点的地址(若是该链表带结点,则保存的是头结点的地址),也就是头引用的值。带头结点的循环链表(Circular Linked List)如图所示。