基于C#实现块状链表

在数据结构的世界里,我们会认识各种各样的数据结构,每一种数据结构都能解决相应领域的问题,当然每个数据结构,有他的优点,必然就有它的缺点,那么如何创造一种数据结构来将某两种数据结构进行扬长避短,那就非常完美了。这样的数据结构也有很多,比如:双端队列,还有就是今天讲的块状链表。
我们都知道
数组 具有 O(1)的查询时间,O(N)的删除,O(N)的插入。。。
链表 具有 O(N)的查询时间,O(1)的删除,O(1)的插入。。。
那么现在我们就有想法了,何不让“链表”和“数组”结合起来,来一起均摊 CURD 的时间,做法将数组进行分块,然后用指针相连接,比如我有 N=100 个元素,那么最理想情况下,我就可以将数组分成 x=10 段,每段 b=10 个元素(排好序),那么我可以用 √N 的时间找到段,因为段中的元素是已经排好序的,所以可以用 lg√N 的时间找到段中的元素,那么最理想的复杂度为 √N+lg√N≈√N。。。
下面我们看看怎么具体使用:

一、结构定义

这个比较简单,我们在每个链表节点中定义一个 头指针,尾指针和一个数组节点。

 public class BlockLinkNode
 {
     /// <summary>
     /// 指向前一个节点的指针
     /// </summary>
     public BlockLinkNode prev;

     /// <summary>
     /// 指向后一个节点的指针
     /// </summary>
     public BlockLinkNode next;

     /// <summary>
     /// 链表中的数组
     /// </summary>
     public List<int> list;
 }

二、插入

刚才也说了,每个链表节点的数据是一个数组块,那么问题来了,我们是根据什么将数组切开呢?总不能将所有的数据都放在一个链表的节点吧,那就退化成数组了,在理想的情况下,为了保持 √N 的数组个数,所以我们定了一个界限 2√N,当链表中的节点数组的个数超过 2√N 的时候,当下次插入数据的时候,我们有两种做法:

  1. 在元素的数组插入处,将当前数组切开,插入元素处之前为一个链表节点,插入元素后为一个链表节点。
  2. 将元素插入数组后,将数组从中间位置切开。

image.png

 /// <summary>
 /// 添加元素只会进行块状链表的分裂
 /// </summary>
 /// <param name="node"></param>
 /// <param name="num"></param>
 /// <returns></returns>
 private BlockLinkNode Add(BlockLinkNode node, int num)
 {
     if (node == null)
     {
         return node;
     }
     else
     {
         /*
          *  第一步:找到指定的节点
          */
         if (node.list.Count == 0)
         {
             node.list.Add(num);

             total = total + 1;

             return node;
         }

         //下一步:再比较是否应该分裂块
         var blockLen = (int)Math.Ceiling(Math.Sqrt(total)) * 2;

         //如果该节点的数组的最后位置值大于插入值,则此时我们找到了链表的插入节点,
         //或者该节点的next=null,说明是最后一个节点,此时也要判断是否要裂开
         if (node.list[node.list.Count - 1] > num || node.next == null)
         {
             node.list.Add(num);

             //最后进行排序下,当然可以用插入排序解决,O(N)搞定
             node.list = node.list.OrderBy(i => i).ToList();

             //如果该数组里面的个数大于2*blockLen,说明已经过大了,此时需要对半分裂
             if (node.list.Count > blockLen)
             {
                 //先将数据插入到数据库
                 var mid = node.list.Count / 2;

                 //分裂处的前段部分
                 var firstList = new List<int>();

                 //分裂后的后段部分
                 var lastList = new List<int>();

                 //可以在插入点处分裂,也可以对半分裂(这里对半分裂)
                 firstList.AddRange(node.list.Take(mid));
                 lastList.AddRange(node.list.Skip(mid).Take(node.list.Count - mid));


                 //开始分裂节点,需要新开辟一个新节点
                 var nNode = new BlockLinkNode();

                 nNode.list = lastList;
                 nNode.next = node.next;
                 nNode.prev = node;

                 //改变当前节点的next和list
                 node.list = firstList;
                 node.next = nNode;
             }

             total = total + 1;

             return node;
         }

         return Add(node.next, num);
     }
 }

二、删除

跟插入道理一样,既然有裂开,就有合并,同样也定义了一个界限值 √N /2 ,当链表数组节点的数组个数小于这个界限值的时候,需要将此节点和后面的链表节点进行合并。
image.png

 /// <summary>
 /// 从块状链表中移除元素,涉及到合并
 /// </summary>
 /// <param name="node"></param>
 /// <param name="num"></param>
 /// <returns></returns>
 private BlockLinkNode Remove(BlockLinkNode node, int num)
 {
     if (node == null)
     {
         return node;
     }
     else
     {
         //第一步: 判断删除元素是否在该节点内
         if (node.list.Count > 0 && num >= node.list[0] && num <= node.list[node.list.Count - 1])
         {
             //定义改节点的目的在于防止remove方法假删除的情况发生
             var prevcount = node.list.Count;

             node.list.Remove(num);

             total = total - (prevcount - node.list.Count);

             //下一步: 判断是否需要合并节点
             var blockLen = (int)Math.Ceiling(Math.Sqrt(total) / 2);

             //如果当前节点的数组个数小于 blocklen的话,那么此时改节点需要和后一个节点进行合并
             //如果该节点时尾节点,则放弃合并
             if (node.list.Count < blockLen)
             {
                 if (node.next != null)
                 {
                     node.list.AddRange(node.next.list);

                     //如果下一个节点的下一个节点不为null,则将下下个节点的prev赋值
                     if (node.next.next != null)
                         node.next.next.prev = node;

                     node.next = node.next.next;
                 }
                 else
                 {
                     //最后一个节点不需要合并,如果list=0,则直接剔除该节点
                     if (node.list.Count == 0)
                     {
                         if (node.prev != null)
                             node.prev.next = null;

                         node = null;
                     }
                 }
             }

             return node;
         }

         return Remove(node.next, num);
     }
 }

三、查询

在理想的情况下,我们都控制在 √N,然后就可以用 √N 的时间找到区块,lg√N 的时间找到区块中的指定值,当然也有人在查询的时候做 链表的合并和分裂,这个就有点像伸展树一样,在查询的时候动态调整,拼的是均摊情况下的复杂度。

 public string Get(int num)
 {
     var blockIndex = 0;
     var arrIndex = 0;

     var temp = blockLinkNode;

     while (temp != null)
     {
         //判断是否在该区间内
         if (temp.list.Count > 0 && num >= temp.list[0] && num <= temp.list[temp.list.Count - 1])
         {
             arrIndex = temp.list.IndexOf(num);

             return string.Format("当前数据在第{0}块中的{1}个位置", blockIndex, arrIndex);
         }

         blockIndex = blockIndex + 1;
         temp = temp.next;
     }

     return string.Empty;
 }

好了,CURD 都分析好了,到这里大家应该对 块状链表有个大概的认识了吧,这个代码是我下午抽闲写的,没有仔细测试,最后是总的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {
             List<int> list = new List<int>() { 8959, 30290, 18854, 7418, 28749, 17313, 5877, 27208, 15771, 4335 };
 
             //list.Clear();
 
             //List<int> list = new List<int>();
 
             //for (int i = 0; i < 100; i++)
             //{
             //    var num = new Random((int)DateTime.Now.Ticks).Next(0, short.MaxValue);
 
             //    System.Threading.Thread.Sleep(1);
 
             //    list.Add(num);
             //}
 
 
             BlockLinkList blockList = new BlockLinkList();
 
             foreach (var item in list)
             {
                 blockList.Add(item);
             }
 
             //var b = blockList.IsExist(333);
             //blockList.GetCount();
 
             Console.WriteLine(blockList.Get(27208));
 
 
             #region MyRegion
             随机删除150个元素
             //for (int i = 0; i < 5000; i++)
             //{
             //    var rand = new Random((int)DateTime.Now.Ticks).Next(0, list.Count);
 
             //    System.Threading.Thread.Sleep(2);
 
             //    Console.WriteLine("\n**************************************\n当前要删除元素:{0}", list[rand]);
 
             //    blockList.Remove(list[rand]);
 
             //    Console.WriteLine("\n\n");
 
             //    if (blockList.GetCount() == 0)
             //    {
             //        Console.Read();
             //        return;
             //    }
             //} 
             #endregion
 
             Console.Read();
         }
     }
 
     public class BlockLinkList
     {
         BlockLinkNode blockLinkNode = null;
 
         public BlockLinkList()
         {
             //初始化节点
             blockLinkNode = new BlockLinkNode()
             {
                 list = new List<int>(),
                 next = null,
                 prev = null
             };
         }
 
         /// <summary>
         /// 定义块状链表的总长度
         /// </summary>
         private int total;
 
         public class BlockLinkNode
         {
             /// <summary>
             /// 指向前一个节点的指针
             /// </summary>
             public BlockLinkNode prev;
 
             /// <summary>
             /// 指向后一个节点的指针
             /// </summary>
             public BlockLinkNode next;
 
             /// <summary>
             /// 链表中的数组
             /// </summary>
             public List<int> list;
         }

         /// <summary>
         /// 判断指定元素是否存在
         /// </summary>
         /// <param name="num"></param>
         /// <returns></returns>
         public bool IsExist(int num)
         {
             var isExist = false;
 
             var temp = blockLinkNode;
 
             while (temp != null)
             {
                 //判断是否在该区间内
                 if (temp.list.Count > 0 && num >= temp.list[0] && num <= temp.list[temp.list.Count - 1])
                 {
                     isExist = temp.list.IndexOf(num) > 0 ? true : false;
 
                     return isExist;
                 }
 
                 temp = temp.next;
             }
 
             return isExist;
         }
 
         public string Get(int num)
         {
             var blockIndex = 0;
             var arrIndex = 0;
 
             var temp = blockLinkNode;
 
             while (temp != null)
             {
                 //判断是否在该区间内
                 if (temp.list.Count > 0 && num >= temp.list[0] && num <= temp.list[temp.list.Count - 1])
                 {
                     arrIndex = temp.list.IndexOf(num);
 
                     return string.Format("当前数据在第{0}块中的{1}个位置", blockIndex, arrIndex);
                 }
 
                 blockIndex = blockIndex + 1;
                 temp = temp.next;
             }
 
             return string.Empty;
         }
 
         /// <summary>
         /// 将元素加入到块状链表中
         /// </summary>
         /// <param name="num"></param>
         public BlockLinkNode Add(int num)
         {
             return Add(blockLinkNode, num);
         }
 
         /// <summary>
         /// 添加元素只会进行块状链表的分裂
         /// </summary>
         /// <param name="node"></param>
         /// <param name="num"></param>
         /// <returns></returns>
         private BlockLinkNode Add(BlockLinkNode node, int num)
         {
             if (node == null)
             {
                 return node;
             }
             else
             {
                 /*
                  *  第一步:找到指定的节点
                  */
                 if (node.list.Count == 0)
                 {
                     node.list.Add(num);
 
                     total = total + 1;
 
                     return node;
                 }
 
                 //下一步:再比较是否应该分裂块
                 var blockLen = (int)Math.Ceiling(Math.Sqrt(total)) * 2;
 
                 //如果该节点的数组的最后位置值大于插入值,则此时我们找到了链表的插入节点,
                 //或者该节点的next=null,说明是最后一个节点,此时也要判断是否要裂开
                 if (node.list[node.list.Count - 1] > num || node.next == null)
                 {
                     node.list.Add(num);
 
                     //最后进行排序下,当然可以用插入排序解决,O(N)搞定
                     node.list = node.list.OrderBy(i => i).ToList();
 
                     //如果该数组里面的个数大于2*blockLen,说明已经过大了,此时需要对半分裂
                     if (node.list.Count > blockLen)
                     {
                         //先将数据插入到数据库
                         var mid = node.list.Count / 2;
 
                         //分裂处的前段部分
                         var firstList = new List<int>();
 
                         //分裂后的后段部分
                         var lastList = new List<int>();
 
                         //可以在插入点处分裂,也可以对半分裂(这里对半分裂)
                         firstList.AddRange(node.list.Take(mid));
                         lastList.AddRange(node.list.Skip(mid).Take(node.list.Count - mid));
 
 
                         //开始分裂节点,需要新开辟一个新节点
                         var nNode = new BlockLinkNode();
 
                         nNode.list = lastList;
                         nNode.next = node.next;
                         nNode.prev = node;
 
                         //改变当前节点的next和list
                         node.list = firstList;
                         node.next = nNode;
                     }
 
                     total = total + 1;
 
                     return node;
                 }
 
                 return Add(node.next, num);
             }
         }
 
         /// <summary>
         /// 从块状链表中移除元素
         /// </summary>
         /// <param name="num"></param>
         /// <returns></returns>
         public BlockLinkNode Remove(int num)
         {
             return Remove(blockLinkNode, num);
         }
 
         /// <summary>
         /// 从块状链表中移除元素,涉及到合并
         /// </summary>
         /// <param name="node"></param>
         /// <param name="num"></param>
         /// <returns></returns>
         private BlockLinkNode Remove(BlockLinkNode node, int num)
         {
             if (node == null)
             {
                 return node;
             }
             else
             {
                 //第一步: 判断删除元素是否在该节点内
                 if (node.list.Count > 0 && num >= node.list[0] && num <= node.list[node.list.Count - 1])
                 {
                     //定义改节点的目的在于防止remove方法假删除的情况发生
                     var prevcount = node.list.Count;
 
                     node.list.Remove(num);
 
                     total = total - (prevcount - node.list.Count);
 
                     //下一步: 判断是否需要合并节点
                     var blockLen = (int)Math.Ceiling(Math.Sqrt(total) / 2);
 
                     //如果当前节点的数组个数小于 blocklen的话,那么此时改节点需要和后一个节点进行合并
                     //如果该节点时尾节点,则放弃合并
                     if (node.list.Count < blockLen)
                     {
                         if (node.next != null)
                         {
                             node.list.AddRange(node.next.list);
 
                             //如果下一个节点的下一个节点不为null,则将下下个节点的prev赋值
                             if (node.next.next != null)
                                 node.next.next.prev = node;
 
                             node.next = node.next.next;
                         }
                         else
                         {
                             //最后一个节点不需要合并,如果list=0,则直接剔除该节点
                             if (node.list.Count == 0)
                             {
                                 if (node.prev != null)
                                     node.prev.next = null;
 
                                 node = null;
                             }
                         }
                     }
 
                     return node;
                 }

                 return Remove(node.next, num);
             }
         }
 
         /// <summary>
         /// 获取块状链表中的所有个数
         /// </summary>
         /// <returns></returns>
         public int GetCount()
         {
             int count = 0;
 
             var temp = blockLinkNode;
 
             Console.Write("各节点数据个数为:");
 
             while (temp != null)
             {
                 count += temp.list.Count;
 
                 Console.Write(temp.list.Count + ",");
 
                 temp = temp.next;
             }
 
             Console.WriteLine("总共有:{0} 个元素", count);
 
             return count;
         }
     }
 }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/195526.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构/C++】栈和队列_循环队列

牺牲一个存储单元来判断队满。 #include<iostream> using namespace std; // 循环队列 #define MaxSize 10 typedef int ElemType; typedef struct {ElemType data[MaxSize];int front, rear; } SqQueue; // 初始化队列 void InitQueue(SqQueue &Q) {// 判断队空 Q.…

微服务知识大杂烩

1.什么是微服务? 微服务(Microservices)是一种软件架构风格,将一个大型应用程序划分为一组小型、自治且松耦合的服务。每个微服务负责执行特定的业务功能,并通过轻量级通信机制(如HTTP)相互协作。每个微服务可以独立开发、部署和扩展,使得应用程序更加灵活、可伸缩和可…

Milvus入门手册1.0

一、window环境搭建&#xff08;单机&#xff09; 1、docker安装 略 2、milvus安装 参考文档&#xff1a;https://milvus.io/docs/install_standalone-docker.md tips: &#xff08;1&#xff09;compose.yaml下载比较慢&#xff0c;可以在网络上找一份。 &#xff08;2&…

2023-11-26 LeetCode每日一题(统计子串中的唯一字符)

2023-11-26每日一题 一、题目编号 828. 统计子串中的唯一字符二、题目链接 点击跳转到题目位置 三、题目描述 我们定义了一个函数 countUniqueChars(s) 来统计字符串 s 中的唯一字符&#xff0c;并返回唯一字符的个数。 例如&#xff1a;s “LEETCODE” &#xff0c;则其…

Python字典合并

合并两个有部分key相同的字典&#xff0c;相同key保留两个字典中对应key的较大值。 (笔记模板由python脚本于2023年11月27日 18:12:15创建&#xff0c;本篇笔记适合熟悉Python字典的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Fr…

通过预定义颜色查找表上色_vtkLookupTable_vtkColorTransferFunction

开发环境&#xff1a; Windows 11 家庭中文版Microsoft Visual Studio Community 2019VTK-9.3.0.rc0vtk-example demo解决问题&#xff1a;通过颜色查找表给vtkPlaneSource上色 第一种技术是使用预定义颜色的查找表vtkLookupTable。这包括创建一个查找表并为其分配一组已命名的…

攻关眼科难题!第一届爱尔眼科-四川大学科研基金完成立项

当前我国眼科患者数量不断增长&#xff0c;人民群众对高质量的眼健康的需要不断攀升&#xff0c;而目前国内眼科医疗资源远不能满足需求&#xff0c;疑难眼病诊疗能力及学术科研体系建设仍有较大进步空间。基于此&#xff0c;爱尔眼科携手四川大学共同设立爱尔眼科-四川大学科研…

js moment时间范围拿到中间间隔时间

2023.11.27今天我学习了如何对只返回的开始时间和结束时间做处理&#xff0c;比如后端返回了&#xff1a; [time:{start:202301,end:202311}] 我们需要把中间的间隔渲染出来。 [202301,202302,202303,202304,202305,202306,202307,202308,202309,202310,202311] 利用moment…

搭建你自己的网盘-个人云存储的终极解决方案-nextcloud AIO(二)

今天接着上篇&#xff0c;我们继续来玩nextcloud AIO. 当我们看到这个页面的时候&#xff0c;则证明AIO已经安装好了&#xff0c;登录账号和密码在图上已经标注了。点击open your nextcloud 即可跳转到我们的域名的登录页。 输入用户名和密码后登录即可。 打开前台页面&#x…

【Dockerfile】将自己的项目构建成镜像部署运行

目录 1.Dockerfile 2.镜像结构 3.Dockerfile语法 4.构建Java项目 5.基于Java8构建项目 1.Dockerfile 常见的镜像在DockerHub就能找到&#xff0c;但是我们自己写的项目就必须自己构建镜像了。 而要自定义镜像&#xff0c;就必须先了解镜像的结构才行。 2.镜像结构 镜…

element table滚动条失效

问题描述:给el-table限制高度之后滚动条没了 给看看咋设置的&#xff1a; <el-table:data"tableData"style"width: 100%;"ref"table"max-height"400"sort-change"changeSort">对比了老半天找不出问题&#xff0c;最后…

上门预约互联网干洗店洗鞋店小程序开发

很多时候可能大家的衣服鞋子需要干洗&#xff0c;但又不想出门送去店里&#xff0c;大家就可以使用手机线上下单预约取货&#xff0c;会有专门的人上门来取衣服&#xff0c;让你能够轻松的进行洗护。 闪站侠洗衣洗鞋小程序&#xff0c;提供了足不出户就能预约人员上门去 衣送洗…

解决ansible批量加入新IP涉及known_hosts报错的问题

我们把一批新的IP加入到ansible的hosts文件&#xff0c;比如/etc/ansible/hosts&#xff0c;往往会有这样的提示&#xff0c; 因为本机的~/.ssh/known_hosts文件中并有fingerprint key串&#xff0c;使用ssh连接目标主机时&#xff0c;一般会提示是否将key字符串加入到~/.ssh/…

Java多线程核心技术二-synchronzied同步方法

1 概述 关键字synchronzied保障了原子性、可见性和有序性。 非线程安全问题会在多个线程对同一个对象中的同一个实例变量进行并发访问时发生&#xff0c;产生的后果就是“脏读”&#xff0c;也就是读取到的数据其实是被更改过的。而线程安全是指获取的实例变量的值是经过同步处…

蚂蚁庄园小课堂答题今日答案最新

蚂蚁庄园小课堂答题今日答案最新 温馨提醒&#xff1a;由于本文章会停留在一个固定的更新时间上&#xff0c;包含当前日期最新的支付宝蚂蚁庄园小课堂答题今日答案。如果您看到这篇文章已成为过去时&#xff0c;请按下面的方法进入查看天天保持更新的最新今日答案&#xff1b; …

Martin Fowler:数字化时代,远程与本地协同工作孰优孰劣?(2)| IDCF

作者&#xff1a;Martin Fowler 译者&#xff1a;冬哥 原文&#xff1a;https://martinfowler.com/articles/remote-or-co-located.html &#xff08;接上篇 &#xff09; 二、大多数人在同地办公时工作效率更高 与软件开发中的许多主题一样&#xff0c;我不能拿 100 个软…

NX二次开发UF_CURVE_convert_conic_to_std 函数介绍

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan UF_CURVE_convert_conic_to_std Defined in: uf_curve.h int UF_CURVE_convert_conic_to_std(UF_CURVE_genconic_p_t gen_conic_data, UF_CURVE_conic_t * conic_data, logical * se…

15:00面试,15:06就出来了,问的问题有点变态。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到8月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%…

自动化测试|我为什么从Cypress转到了Playwright?

以下为作者观点&#xff1a; 早在2019年&#xff0c;我就开始使用Cypress &#xff0c;当时我所在的公司决定在新项目中放弃Protractor 。当时&#xff0c;我使用的框架是Angular&#xff0c;并且有机会实施Cypress PoC。最近&#xff0c;我换了工作&#xff0c;现在正在使用R…

MEFLUT: Unsupervised 1D Lookup Tables for Multi-exposure Image Fusion

Abstract 在本文中&#xff0c;我们介绍了一种高质量多重曝光图像融合&#xff08;MEF&#xff09;的新方法。我们表明&#xff0c;曝光的融合权重可以编码到一维查找表&#xff08;LUT&#xff09;中&#xff0c;该表将像素强度值作为输入并产生融合权重作为输出。我们为每次…