using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataStructure { class MySingleLinkedList<T> : MyIList<T> { private Node<T> head; public Node<T> Head { get { return head; } set { head = value; } } public MySingleLinkedList() { head = null; } public MySingleLinkedList(Node<T> p) { head = p; } /// <summary> /// 求单链表的长度 /// 求单链表的长度与顺序表不同。顺序表可以通过指示表中最后一个数据元素的last直接求得,因为顺序表所占用的空间是连续的空间,而单链表需要从头引用开始,一个结点一个结点遍历,直到表的末尾。 /// </summary> /// <returns></returns> public int GetLength() { if (IsEmpty()) { return 0; } else { Node<T> p = head; int i = 1; while (p.Next != null) { p = p.Next; i++; } return i; #region 人家是这么写的 //int len = 0; //while (p != null) //{ // ++len; // p = p.Next; //} //return len; #endregion } } /// <summary> /// 清空操作 /// 清空操作是指清除单链表中的所有结点使单链表为空,此时,头引用head为null。 /// </summary> public void Clear() { head = null; } /// <summary> /// 判断单链表是否为空 /// 如果单链表的头引用为null,则单链表为空,返回true,否则返回false。 /// 判断单链表是否为空的算法实现如下 /// </summary> /// <returns></returns> public bool IsEmpty() { if (head == null) { return true; } return false; } /// <summary> /// 附加操作 /// 单链表的附加操作也需要从单链表的头引用开始遍历单链表,直到单链表的末尾,然后在单链表的末端添加一个新结点。 /// </summary> /// <param name="item"></param> public bool Append(T item) { Node<T> appendItem = new Node<T>(item); if (IsEmpty()) { head = appendItem; } else { Node<T> theLastItem = new Node<T>(); theLastItem = head; while (theLastItem.Next != null) { theLastItem = theLastItem.Next; } theLastItem.Next = appendItem; } return true; } /// <summary> /// 插入操作 /// 单链表的插入操作是指在表的第i个位置结点处插入一个值为item的新结点。插入操作需要从单连表的头引用开始遍历,直到找到第i个位置的结点。插入操作分为在结点之前插入的前插操作和在结点之后插入的后插操作。 /// 注意点: /// 1插入要考虑空,满 /// 2插入要考虑输入位置是否正确 /// 3要把前继节点给具体显式声明出来,这样比较清晰! /// 我这里做的是《前插》操作!!!!!!!! /// </summary> /// <param name="item"></param> /// <param name="i"></param> public void Insert(T item, int i) { int len = GetLength(); if (len == 0) { Console.WriteLine("单链表为空,不存在插入过程!"); } else if (i <= 0 || i > len) { Console.WriteLine("你输入的位置有错误!!!"); } else { //插头部(这样对于我的逻辑来说清晰一点) if (i == 1) { Node<T> temp = new Node<T>(); temp = head; head = new Node<T>(item); head.Next = temp; } else { Node<T> p = head; int j = 1; while (p.Next != null) { if (++j == i) { //找到了要插入的位置的前面一个节点 Node<T> temp = new Node<T>(); temp = p.Next; p.Next = new Node<T>(item); p.Next.Next = temp; //完成了插入工作,退出循环! break; } p = p.Next; } } } #region 教科书的前插操作 // 前插操作 //前插操作需要查找第i个位置的结点的直接前驱。设p指向第i个位置的结点,q指向待插入的新结点,r指向p的直接前驱结点,将q插在p之前的操作如图2.8所示。如果要在第一个结点之前插入新结点,则需要把p结点的地址保存在q的引用域中,然后把p的地址保存在头引用中。 // public void Insert(T item, int i) //{ //if (IsEmpty() || i < 1) //{ //Console.WriteLine(“List is empty or Position is error!”); //return; //} //if (i == 1) //{ //Node<T> q = new Node<T>(item); //q.Next = head; //head = q; //return; //} //Node<T> p = head; //Node<T> r = new Node<T>(); //int j = 1; //while (p.Next != null&& j < i) //{ //r = p; //p = p.Next; //++j; //} //if (j == i) //{ //Node<T> q = new Node<T>(item); //q.Next = p; //r.Next = q; //} //else //{ //Console.Writeline(“Position is error!”); //} //return; //} #endregion } /// <summary> /// 删除操作 /// 单链表的删除操作是指删除第i个结点,返回被删除结点的值。删除操作也需要从头引用开始遍历单链表,直到找到第i个位置的结点。如果i为1,则要删除第一个结点,则需要把该结点的直接后继结点的地址赋给头引用。对于其它结点,由于要删除结点,所以在遍历过程中需要保存被遍历到的结点的直接前驱,找到第i个结点后,把该结点的直接后继作为该结点的直接前驱的直接后继。删除操作如图2.10所示。 /// </summary> /// <param name="i"></param> /// <returns></returns> public bool Delete(int i) { int len = GetLength(); if (IsEmpty()) { Console.WriteLine("单链表为空,根本不存在删除操作"); return false; } else if (i <= 0 || i > len) { Console.WriteLine("输入的位置有误!!!!!"); return false; } else { if (i == 1) { head = head.Next; } else { Node<T> beforeDelete = new Node<T>(); Node<T> currentNode = new Node<T>(); currentNode = head; int currentIndex = 1; while (currentNode.Next != null && currentIndex < i) { beforeDelete = currentNode; currentNode = currentNode.Next; currentIndex++; } //这样写比较有逻辑性一点 //先通过while,找出要删除的节点之前的那一个节点 //while,只负责此逻辑功能 //不像我的《插入》写法,显得比较乱 //这点仁者见仁智者见智 beforeDelete.Next = currentNode.Next; } return true; } } /// <summary> /// 取表元 /// 取表元运算是返回单链表中第i个结点的值。与插入操作一样,时间主要消耗在结点的遍历上。如果表为空则不进行遍历。当表非空时,i等于1遍历的结点数最少(1个),i等于n遍历的结点数最多(n个,n为单链表的长度),平均遍历的结点数为n/2。所以,取表元运算的时间复杂度为O(n)。 /// </summary> /// <param name="i"></param> /// <returns></returns> public T GetElem(int i) { int len = GetLength(); if (IsEmpty()) { Console.WriteLine("单链表是空的,没什么好取出来!"); return default(T); } if (i <= 0 || i > len) { Console.WriteLine("你输入的位置有误!"); return default(T); } else { Node<T> p = head; int j = 1; //这种循环的写法 //在单链表的操作中 //尤其常见 while (p.Next != null && j < i) { p = p.Next; j++; } return p.Data; } } /// <summary> /// 按值查找 /// 单链表中的按值查找是指在表中查找其值满足给定值的结点。由于单链表的存储空间是非连续的,所以,单链表的按值查找只能从头引用开始遍历,依次将被遍历到的结点的值与给定值比较,如果相等,则返回在单序表中首次出现与给定值相等的数据元素的序号,称为查找成功;否则,在单链表中没有值与给定值匹配的结点,返回一个特殊值表示查找失败。 /// </summary> /// <param name="value"></param> /// <returns></returns> public int Locate(T value) { bool haveFound = false; Node<T> p = new Node<T>(); p = head; int j = 1; while (p.Next != null) { if (p.Data.Equals(value)) { haveFound = true; break; } p = p.Next; j++; } if (haveFound) return j; else return -1; #region 教科书错误的写法,如果没有找到,会返回最后一个元素的index // public int Locate(T value) //{ //if(IsEmpty()) //{ //Console.WriteLine("List is Empty!"); //return -1; //} //Node<T> p = new Node<T>(); //p = head; //int i = 1; //while (!p.Data.Equals(value)&& p.Next != null) //{ // P = p.Next; //++i; //} //return i; //} #endregion } } public class Node<T> { private Node<T> next; public Node<T> Next { get { return next; } set { next = value; } } private T data; public T Data { get { return data; } set { data = value; } } public Node(T t) { data = t; next = null; } public Node() { // TODO: Complete member initialization next = null; } } }