剑指offer中面试题3,在二维数组中的查找。
题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断,数组中是否含有该整数。
例如:
1 | 2 | 8 | 9 |
2 | 4 | 9 | 12 |
4 | 7 | 10 | 13 |
6 | 8 | 11 | 15 |
书中给出的算法是从右上角数字开始查找,并给出了C++代码。
我从左下角给出C#的实现,先不要急着看代码,先看我的解释。
1 public class Matrix : IFindMatrix 2 { 3 private static Matrix instance = new Matrix(); 4 public static Matrix Instance 5 { 6 get 7 { 8 return instance; 9 } 10 } 11 int[,] intMatrix; 12 int rows, cols; 13 bool noFind; 14 public Matrix() { } 15 public Matrix(int[,] matrix, int r, int c) 16 { 17 if (matrix == null || r <= 0 || c <= 0) 18 { 19 noFind = true; 20 } 21 else 22 { 23 intMatrix = matrix; 24 rows = r; 25 cols = c; 26 noFind = false; 27 } 28 } 29 public bool FindInMatrix(int number,ref int findCount) 30 { 31 if (noFind) 32 { 33 findCount = -1; 34 return false; 35 } 36 findCount = 0; 37 int i = rows - 1; 38 int j = 0; 39 bool exist = false; 40 do 41 { 42 43 if (intMatrix[i, j] == number) 44 { 45 findCount++; 46 exist = true; 47 break; 48 } 49 else if (intMatrix[i, j] > number) 50 { 51 findCount++; 52 i--; 53 } 54 else 55 { 56 findCount++; 57 j++; 58 } 59 } while (i >= 0 && j < cols); 60 return exist; 61 } 62 public bool OptFindInMatrix(int number,ref int findCount) 63 { 64 65 if (noFind) 66 { 67 findCount = -1; 68 return false; 69 } 70 findCount = 0; 71 int mini, minj, maxi, maxj; 72 mini = 0; 73 minj = 0; 74 maxi = rows - 1; 75 maxj = cols - 1; 76 int i = rows - 1; 77 int j = 0; 78 bool exist = false; 79 do 80 { 81 82 if ( intMatrix[maxi,maxj] < number) 83 { 84 findCount++; 85 break; 86 } 87 if (intMatrix[mini, minj] > number) 88 { 89 findCount++; 90 break; 91 } 92 if (intMatrix[i,j] == number) 93 { 94 findCount++; 95 exist = true; 96 break; 97 } 98 else if (intMatrix[i,j] > number) 99 { 100 findCount++; 101 i--; 102 maxi = i; 103 } 104 else 105 { 106 findCount++; 107 j++; 108 minj = j; 109 } 110 } while (i >= 0 && j < cols); 111 return exist; 112 } 113 public bool FindInMatrix(int number) 114 { 115 if (noFind) 116 { 117 return false; 118 } 119 int i = rows - 1; 120 int j = 0; 121 bool exist = false; 122 do 123 { 124 if (intMatrix[i, j] == number) 125 { 126 exist = true; 127 break; 128 } 129 else if (intMatrix[i, j] > number) 130 { 131 i--; 132 } 133 else 134 { 135 j++; 136 } 137 } while (i >= 0 && j < cols); 138 return exist; 139 } 140 public bool OptFindInMatrix(int number) 141 { 142 if (noFind) 143 { 144 return false; 145 } 146 int mini, minj, maxi, maxj; 147 mini = 0; 148 minj = 0; 149 maxi = rows - 1; 150 maxj = cols - 1; 151 int i = rows - 1; 152 int j = 0; 153 bool exist = false; 154 do 155 { 156 if (intMatrix[mini, minj] > number || intMatrix[maxi, maxj] < number) 157 break; 158 if (intMatrix[i, j] == number) 159 { 160 exist = true; 161 break; 162 } 163 else if (intMatrix[i, j] > number) 164 { 165 i--; 166 maxi = i; 167 } 168 else 169 { 170 j++; 171 minj = j; 172 } 173 } while (i >= 0 && j < cols); 174 return exist; 175 } 176 public void InitMatrix(int[,] matrix, int r, int c) 177 { 178 if (matrix == null || r <= 0 || c <= 0) 179 { 180 noFind = true; 181 } 182 else 183 { 184 intMatrix = matrix; 185 rows = r; 186 cols = c; 187 noFind = false; 188 } 189 } 190 191 }
按照剑指offer实现了单例模式(只是为了练手,感觉没有什么作用),至于继承的接口是为了方便测试用的。
从左上角或者右下角开始扫描的话可以一次减少一行或者一列的比较数量。但是从左上角和右下角呢?左上角是矩阵中的最小值,右下角是矩阵中的最大值,如果要寻找的number小于左上角或者大于右下角的话,就可以直接返回false了?
在每次从左下角或者右上角扫描时,可以先判断number与左上角和右下角的比值,判断是否直接返回false。所以加了一个OptFindInMatrix(...)函数代表是优化的以前算法的意思,但是更想知道这种所谓的优化究竟效果如何,所以给OptFindInMatrix(....)增加了一个参数findCount,用来记录查找所增加的次数。
写了一个测试类,一个100*100的矩阵,矩阵每个元素的赋值公式见下面伪代码。
//伪代码,告诉矩阵值的计算方式 int id = 0; for (int i = 0; i < 100; i++) for (int j = 0; j < 100; j++) { id++; a[i][j]=i*j+id; }
测试类顺便练习使用的XML的存储,使用XML保存了测试数据和测试结构,xml的使用方法见代码 ,如果要运行代码,首先要调用一下InitTestData()函数生成测试数据,当然你还可以通过其他的公式生成测试数据。
测试类里面有一个接口InFindMatrix,测试类构造函数里面把Matrix 的实例赋值给 InFindMatrix,测试函数只使用用InFindMatrix。
测试类test()可以实现各种测试用例。下见代码。
1 public class MatrixTest 2 { 3 private static readonly int rows = 100; 4 private static readonly int cols = 100; 5 private static readonly int count = 10000; 6 public IFindMatrix InFindMatrix; 7 XmlDocument xmlTestDoc; 8 XmlDocument XmlResultData; 9 private void InitTestData() 10 { 11 xmlTestDoc = new XmlDocument(); 12 xmlTestDoc.AppendChild(xmlTestDoc.CreateXmlDeclaration("1.0", "utf-8", null)); 13 XmlElement TestDataSet = xmlTestDoc.CreateElement("TestDataSet"); 14 XmlAttribute attCount = xmlTestDoc.CreateAttribute("count"); 15 attCount.Value = count.ToString(); 16 TestDataSet.Attributes.Append(attCount); 17 XmlAttribute attRows = xmlTestDoc.CreateAttribute("rows"); 18 attRows.Value = rows.ToString(); 19 TestDataSet.Attributes.Append(attRows); 20 XmlAttribute attCols = xmlTestDoc.CreateAttribute("cols"); 21 attCols.Value = cols.ToString(); 22 TestDataSet.Attributes.Append(attCols); 23 xmlTestDoc.AppendChild(TestDataSet); 24 25 int id = 0; 26 for (int i = 0; i < rows; i++) 27 for (int j = 0; j < cols; j++) 28 { 29 id++; 30 //testData 31 XmlElement eleTestItem = xmlTestDoc.CreateElement("TestData"); 32 XmlAttribute attId = xmlTestDoc.CreateAttribute("id"); 33 attId.Value = id.ToString(); 34 XmlAttribute attI = xmlTestDoc.CreateAttribute("i"); 35 attI.Value = i.ToString(); 36 XmlAttribute attJ = xmlTestDoc.CreateAttribute("j"); 37 attJ.Value = j.ToString(); 38 XmlAttribute attValue = xmlTestDoc.CreateAttribute("value"); 39 attValue.Value = (i * j + id).ToString(); 40 41 eleTestItem.Attributes.Append(attId); 42 eleTestItem.Attributes.Append(attI); 43 eleTestItem.Attributes.Append(attJ); 44 eleTestItem.Attributes.Append(attValue); 45 TestDataSet.AppendChild(eleTestItem); 46 } 47 xmlTestDoc.Save("testDataSet.xml"); 48 49 } 50 public int[,] LoadData(ref int rows, ref int cols) 51 { 52 int[,] matrix; 53 rows = cols = -1; 54 xmlTestDoc = new XmlDocument(); 55 xmlTestDoc.Load("testDataSet.xml"); 56 XmlNode xmln = xmlTestDoc.SelectSingleNode("/TestDataSet"); 57 string str = (xmln as XmlElement).GetAttribute("rows").ToString(); 58 if (!int.TryParse(str, out rows)) 59 return null; 60 str = (xmln as XmlElement).GetAttribute("cols").ToString(); 61 if (!int.TryParse(str, out cols)) 62 return null; 63 if (rows <= 0 || cols <= 0) 64 { 65 return null; 66 } 67 matrix = new int[rows, cols]; 68 XmlNodeList xnl = xmlTestDoc.SelectNodes("/TestDataSet/TestData"); 69 XmlElement ele; 70 int i, j, value; 71 foreach (XmlNode xn in xnl) 72 { 73 ele = (xn as XmlElement); 74 str = ele.GetAttribute("i"); 75 int.TryParse(str, out i); 76 str = ele.GetAttribute("j"); 77 int.TryParse(str, out j); 78 str = ele.GetAttribute("value"); 79 int.TryParse(str, out value); 80 matrix[i, j] = value; 81 } 82 return matrix; 83 } 84 public MatrixTest() 85 { 86 InFindMatrix = Matrix.Instance; 87 int rows, cols; 88 rows = cols = -1; 89 int[,] matrix = LoadData(ref rows, ref cols); 90 InFindMatrix.InitMatrix(matrix, rows, cols); 91 } 92 public void test() 93 { 94 XmlResultData = new XmlDocument(); 95 XmlResultData.AppendChild(XmlResultData.CreateXmlDeclaration("1.0", "utf-8", null)); 96 XmlElement ResultDataSet = XmlResultData.CreateElement("ResultDataSet"); 97 XmlResultData.AppendChild(ResultDataSet); 98 99 int falseCount = 0; 100 double betterCount = 0; 101 int whileNum = 0; 102 while (whileNum < 20000) 103 { 104 XmlElement ele = XmlResultData.CreateElement("testResult"); 105 XmlElement eleFind = XmlResultData.CreateElement("FindValue"); 106 eleFind.InnerText = whileNum.ToString(); 107 ele.AppendChild(eleFind); 108 XmlElement eleExpect; 109 XmlElement eleActual; 110 XmlElement eleFindCount; 111 XmlElement eleOptFindCount; 112 bool bExpect = true; 113 bool bActual = false; 114 int FindCount, OptFindCount; 115 FindCount = OptFindCount = -1; 116 bExpect = InFindMatrix.FindInMatrix(whileNum, ref FindCount); 117 bActual = InFindMatrix.OptFindInMatrix(whileNum, ref OptFindCount); 118 if (bExpect != bActual) 119 { 120 Console.WriteLine("mFind算法出现错误"); 121 Console.WriteLine("OptFindCount算法出现错误"); 122 Console.ReadLine(); 123 } 124 if (bActual) 125 { 126 betterCount = betterCount - FindCount*2; 127 } 128 else 129 { 130 falseCount++; 131 betterCount = betterCount + (FindCount - OptFindCount) * 2.5 - OptFindCount * 1.5; 132 } 133 eleExpect = XmlResultData.CreateElement("Expect"); 134 eleExpect.InnerText = bExpect.ToString(); 135 ele.AppendChild(eleExpect); 136 eleActual = XmlResultData.CreateElement("Actual"); 137 eleActual.InnerText = bActual.ToString(); 138 ele.AppendChild(eleActual); 139 eleFindCount = XmlResultData.CreateElement("FindCount"); 140 eleFindCount.InnerText = FindCount.ToString(); 141 ele.AppendChild(eleFindCount); 142 eleOptFindCount = XmlResultData.CreateElement("OptFindCount"); 143 eleOptFindCount.InnerText = OptFindCount.ToString(); 144 ele.AppendChild(eleOptFindCount); 145 ResultDataSet.AppendChild(ele); 146 XmlResultData.AppendChild(ResultDataSet); 147 whileNum++; 148 } 149 XmlResultData.Save("testResult.xml"); 150 Console.WriteLine(betterCount); 151 } 152 153 }
100*100矩阵,矩阵中元素值的跨度1~10000+99*99,测试数据是从0~20000,把OptFindInMatrix和FindInMatrix每次的寻找次数求和查看结果,根据测试结果实际上所谓的优化OptFindInMatrix(...)函数的比较次数总数是要远多于FindInMatrix(...),平均寻找每个数OptFindInMatrix要比FindInMatrix多193次比较
测试数据0~40000时,OptFindInMatrix(...)函数的比较次数总数小于FindInMatrix(...)平均寻找每个数OptFindInMatrix要比FindInMatrix少26次比较.
因为如果要找的数据再矩阵中时,OptFindInMatrix要比FindInMatrix多两次比较的次数,因此具体的OptFindInMatrix是否可以减少比较次数要和矩阵的数据和查找数结合起来,就测试数据来讲,矩阵中元素值的跨度1~10000+99*99,测试数据是从0~40000时,OptFindInMatrix和FindInMatrix的比较次数大概相等,要查找的数据范围是矩阵数据范围的两倍时,可能OptFindInMatrix更优秀一点。(实际情况可能更复杂一点)