由于顺序表每次插入新数据的时候后面的数据都要相对应的往后移动一个单位,相当的麻烦,所以单链表出现了,单链表的数据元素存储是由数据+下一级地址(指针域)组成,从元素开始每一级都能找到他的下一级。
物理存储结构:不一定是连续不断的
逻辑存储结构:逻辑存储是连续的,每一级都能够找到对应的下一级元素
使用场景:适用于需要进行大量增加和删除元素操作,而对访问元素无要求的,及预先无法确定表的大小的程序
由于c#里面没有跟c/c++一样的指针,这里使用存储对象来代表指针,因此这里创建单链表使用的是class存储数据结构
首先是创建存储元素机构的类
/// <summary>
/// 单链表
/// </summary>
class LinkList
{
public string data;
public LinkList next;
}
创建操作类和表头
class LinkListClass
{
//表头指针
public LinkList head = new LinkList();
}
插入数据:这里说下两种插入数据的方法,每一种的时间空间复杂度:O(n)
头插法:从head插入元素,每次插入的时候注意新元素优先找到头部下一级的元素地址赋值给新元素的后继地址,然后头部的后继地址变成新元素地址
尾插法:从单链表的尾部插入,首先将最后一个元素的后继地址找到新元素地址,然后把新元素替换成尾部,每次循环这个过程,结束后尾元素的后继地址给空指针
#region 建立单链表-头插法 O(n)
public void CreateListFrist(string[] split)
{
head.next = null;
LinkList s;
for (int i = 0; i < split.Length; i++)
{
//从头部插入数据
s = new LinkList();
s.data = split[i];
s.next = head.next;
//头部的next改变
head.next = s;
}
}
#endregion
#region 建立单链表-尾插法 O(n)
public void CreateListEnd(string[] split)
{
//r存最后一个元素,s存插入元素
LinkList s, r;
r = head;
for (int i = 0; i < split.Length; i++)
{
//从尾部插入数据
s = new LinkList();
s.data = split[i];
r.next = s;
//获取最后一个元素
r = s;
}
//最后一个元素尾部为空
r.next = null;
}
#endregion
然后输出单链表,时间空间复杂度:O(n)#region 输出单链表表 O(n)
public string DispList()
{
string str = "";
LinkList p;
p = head.next;
while (p != null)
{
str += p.data + " ";
p = p.next;
}
return str;
}
#endregion
计算单链表的长度,时间空间复杂度:O(n)
#region 单链表的长度 O(n)
public int ListLength()
{
int i = 0;
LinkList p;
p = head;
while (p.next != null)
{
i++;
p = p.next;
}
return i;
}
#endregion
获取item项的值,时间空间复杂度为:O(n)
#region 获取第item项的值 O(n)
public bool GetElem(int item, ref string e)
{
LinkList p = head;
int j = 0;
if (item < 1)
{
return false;
}
//循环找到item的位置
while (j < item && p != null)
{
j++;
p = p.next;
}
//判断item位置是否存在
if (p == null)
{
return false;
}
else
{
e = p.data;
return true;
}
}
#endregion
根据元素的值获取元素所在的位置,时间空间复杂度为:O(n)
#region 根据元素值获取item位置 O(n)
public int LoateElem(string e)
{
LinkList p = head.next;
int item = 0;
//开始找元素里面的data与e相等
while (p != null && p.data != e)
{
//计数
item++;
//下一个元素
p = p.next;
}
//如果单链表没有元素或者没有找到与单链表匹配的值,输出为0
if (p == null)
{
return 0;
}
else
{
return item;
}
}
#endregion
接下来是指定元素插入,根据单链表的只能找到下一级的特性,每次插入的时候获取下一级的地址给新元素的后继地址,断掉这个链找到新元素地址赋值给后继地址。时间空间复杂度为:O(n)
#region 在item项插入values O(n)
public bool ListInsert(int item, string values)
{
LinkList p, s;
int j = 0;
p = head;
//找到插入地址
while (j < item - 1 && p != null)
{
//下一项
p = p.next;
j++;
}
if (p == null)
{
return false;
}
else
{
s = new LinkList();
s.data = values;
//根据单链表的特性只能从上级找下级,不能下级找到上级,所有这里只能是插入元素先找到下级元素,然后前面的下一级地址指定插入元素
s.next = p.next;
p.next = s;
return true;
}
}
#endregion
最后就是指定元素的删除,这里注意的是删掉元素和上一级是存在的,然后就是怎么删除了,直接是从删除元素位置的上一个的后继地址直接指向删除元素的后继地址,如果删除元素后继地址为空地址,说明是删除的最后一个元素。时间空间复杂度为:O(n)
#region 删除item元素 O(n)
public bool ListDelete(int item, ref string values)
{
LinkList p, s;
int j = 0;
if (item<1)
{
return false;
}
p = head;
while (j<item-1 && p.next!=null)
{
p = p.next;
j++;
}
//删除前一个元素和需要删除的元素不能为空
if (p==null || p.next==null)
{
return false;
}
else
{
//找到需要删除的节点
s = p.next;
values = s.data;
//直接找到删除节点的next
p.next = s.next;
s = null;//释放空间
return true;
}
}
#endregion
结论:单链表相比顺序表新增删除省了不少事,不用每次都要重新操作后面的移位问题,但是相对于单链表内存消耗多了指针域
优点:没有空间限制,存储元素的个数无上限,基本只与内存空间大小有关。插入和删除元素速率高。
缺点:占用额外的空间以存储指针(浪费空间)。随机存取元素速率低。单链表要找一个数,必须要从表头开始找起,十分麻烦。
这里说一下循环单链表,操作跟单链表一样,只是注意尾元素的后继地址不是指向空地址,而是指向head头元素,这里的头元素在循环单链表中做了一个参照物的作用,如果你不知道头元素在哪里,可能操作的时候会出现一直循环下去,最终会导致程序崩溃。